diff --git a/.gitignore b/.gitignore index 13af317f..1864dd01 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,10 @@ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ + +app/schemas/ + +signatures/ + +app/.cxx +/i18n/ diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..9ffcbb8a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Szkolny.eu \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..733f463f --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..276cd1ca --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index e4b2b0ee..2f82ed79 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -50,7 +50,7 @@ - + diff --git a/agendacalendarview/build.gradle b/agendacalendarview/build.gradle index 824ce517..92731fb9 100644 --- a/agendacalendarview/build.gradle +++ b/agendacalendarview/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' //apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk android { lintOptions { @@ -12,7 +12,7 @@ android { defaultConfig { minSdkVersion 14 - targetSdkVersion rootProject.ext.targetSdkVersion + targetSdkVersion setup.targetSdk versionCode 1 versionName "1.0" } @@ -43,9 +43,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Google libraries - implementation "androidx.appcompat:appcompat:${androidXAppCompat}" - implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}" - implementation "com.google.android.material:material:${googleMaterial}" + implementation "androidx.appcompat:appcompat:${versions.appcompat}" + implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}" + implementation "com.google.android.material:material:${versions.material}" // other libraries //implementation 'se.emilsjolander:stickylistheaders:2.7.0' diff --git a/annotation/.gitignore b/annotation/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/annotation/.gitignore @@ -0,0 +1 @@ +/build diff --git a/annotation/build.gradle b/annotation/build.gradle new file mode 100644 index 00000000..ee7f6d51 --- /dev/null +++ b/annotation/build.gradle @@ -0,0 +1,29 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} + +sourceCompatibility = "7" +targetCompatibility = "7" + +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt new file mode 100644 index 00000000..d37e5b0a --- /dev/null +++ b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +package pl.szczodrzynski.edziennik.annotation + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +annotation class SelectiveDao( + val db: KClass<*> +) diff --git a/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt new file mode 100644 index 00000000..224fca1c --- /dev/null +++ b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +package pl.szczodrzynski.edziennik.annotation + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +annotation class UpdateSelective( + val primaryKeys: Array, + val skippedColumns: Array = [] +) diff --git a/app/build.gradle b/app/build.gradle index 11c43b78..47b02774 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,14 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' apply plugin: 'io.fabric' android { signingConfigs { } - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk defaultConfig { applicationId 'pl.szczodrzynski.edziennik' minSdkVersion setup.minSdk @@ -15,6 +16,12 @@ android { versionCode release.versionCode versionName release.versionName multiDexEnabled true + + externalNativeBuild { + cmake { + cppFlags "-std=c++11" + } + } } buildTypes { applicationVariants.all { variant -> @@ -62,6 +69,12 @@ android { packagingOptions { exclude 'META-INF/library-core_release.kotlin_module' } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" + } + } } /*task finalizeBundleDebug(type: Copy) { @@ -91,7 +104,7 @@ tasks.whenTaskAdded { task -> dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - annotationProcessor "androidx.room:room-compiler:${versions.room}" + kapt "androidx.room:room-compiler:${versions.room}" debugImplementation "com.amitshekhar.android:debug-db:1.0.5" implementation "android.arch.navigation:navigation-fragment-ktx:${versions.navigationFragment}" @@ -115,6 +128,7 @@ dependencies { implementation "com.mikepenz:iconics-core:${versions.iconics}" implementation "com.mikepenz:iconics-views:${versions.iconics}" implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar" + implementation "com.mikepenz:materialize:1.2.1" implementation "com.github.kuba2k2:NavLib:${versions.navlib}" @@ -131,7 +145,11 @@ dependencies { implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true} implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update implementation "com.jaredrummler:colorpicker:1.0.2" - implementation "com.squareup.okhttp3:okhttp:3.12.2" + implementation("com.squareup.okhttp3:okhttp") { + version { + strictly "3.12.2" + } + } implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update implementation "com.wdullaer:materialdatetimepicker:4.1.2" implementation "com.yuyh.json:jsonviewer:1.0.6" @@ -153,8 +171,8 @@ dependencies { //implementation project(":Navigation") implementation project(":szkolny-font") - debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1" - releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1" + implementation "com.github.ChuckerTeam.Chucker:library:3.0.1" + //releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1" //implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT' //implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1' @@ -170,6 +188,28 @@ dependencies { implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' implementation 'com.github.kuba2k2:Tachyon:551943a6b5' + + implementation "com.squareup.retrofit2:retrofit:${versions.retrofit}" + implementation "com.squareup.retrofit2:converter-gson:${versions.retrofit}" + + implementation 'com.github.jetradarmobile:android-snowfall:1.2.0' + + implementation "io.coil-kt:coil:0.9.2" + + implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76' + + implementation project(":annotation") + kapt project(":codegen") + + implementation 'com.google.android:flexbox:2.0.1' + + implementation 'com.qifan.powerpermission:powerpermission:1.3.0' + implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0' + + implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT' + implementation 'pl.droidsonroids:jspoon:1.3.2' + implementation "com.squareup.retrofit2:converter-scalars:2.8.1" + implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" } repositories { mavenCentral() diff --git a/app/libs/java-json.jar b/app/libs/java-json.jar new file mode 100644 index 00000000..2f211e36 Binary files /dev/null and b/app/libs/java-json.jar differ diff --git a/app/proguard/app.pro b/app/proguard/app.pro index aeecf18d..68d1fde8 100644 --- a/app/proguard/app.pro +++ b/app/proguard/app.pro @@ -22,12 +22,20 @@ -keep class android.support.v7.widget.** { *; } -keep class pl.szczodrzynski.edziennik.utils.models.** { *; } --keep class pl.szczodrzynski.edziennik.data.db.modules.events.Event { *; } --keep class pl.szczodrzynski.edziennik.data.db.modules.events.EventFull { *; } --keepclassmembers class pl.szczodrzynski.edziennik.widgets.WidgetConfig { public *; } --keepnames class pl.szczodrzynski.edziennik.WidgetTimetable --keepnames class pl.szczodrzynski.edziennik.notifications.WidgetNotifications --keepnames class pl.szczodrzynski.edziennik.luckynumber.WidgetLuckyNumber +-keep class pl.szczodrzynski.edziennik.data.db.entity.Event { *; } +-keep class pl.szczodrzynski.edziennik.data.db.full.EventFull { *; } +-keep class pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage { *; } +-keep class pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel { *; } +-keepclassmembers class pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig { public *; } +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider + +-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); } +-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; } +-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); } + +-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; } -keep class .R -keep class **.R$* { @@ -48,4 +56,14 @@ # Most of volatile fields are updated with AFU and should not be mangled -keepclassmembernames class kotlinx.** { volatile ; -} \ No newline at end of file +} + +-keepclasseswithmembernames class * { + native ; +} + +-keep class pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing { public final byte[] pleaseStopRightNow(java.lang.String, long); } + +-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; } +-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; } +-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo.Platform { *; } diff --git a/app/proguard/zxing.pro b/app/proguard/zxing.pro new file mode 100644 index 00000000..d5eeb97d --- /dev/null +++ b/app/proguard/zxing.pro @@ -0,0 +1,5 @@ +# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} \ No newline at end of file diff --git a/app/sampledata/format-bold/ic_format_bold.xml b/app/sampledata/format-bold/ic_format_bold.xml new file mode 100644 index 00000000..87bc9819 --- /dev/null +++ b/app/sampledata/format-bold/ic_format_bold.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_night.xml b/app/sampledata/format/ic_format_italic.xml similarity index 57% rename from app/src/main/res/drawable/ic_night.xml rename to app/sampledata/format/ic_format_italic.xml index 008a4fbf..b44be3bb 100644 --- a/app/src/main/res/drawable/ic_night.xml +++ b/app/sampledata/format/ic_format_italic.xml @@ -1,3 +1,7 @@ + + + android:pathData="M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z"/> diff --git a/app/sampledata/format/ic_format_underline.xml b/app/sampledata/format/ic_format_underline.xml new file mode 100644 index 00000000..e3d40e63 --- /dev/null +++ b/app/sampledata/format/ic_format_underline.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/sampledata/settings/ic_settings.xml b/app/sampledata/settings/ic_settings.xml new file mode 100644 index 00000000..efaaa4ee --- /dev/null +++ b/app/sampledata/settings/ic_settings.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/sampledata/vulcan/edu.lublin.eu.png b/app/sampledata/vulcan/edu.lublin.eu.png new file mode 100644 index 00000000..ece8b925 Binary files /dev/null and b/app/sampledata/vulcan/edu.lublin.eu.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d842cb6d..6e8fc491 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,20 @@ xmlns:tools="http://schemas.android.com/tools" package="pl.szczodrzynski.edziennik"> + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - + + + + + + - - - + + + @@ -174,8 +98,10 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_notifications_info" /> - + + @@ -185,68 +111,107 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_lucky_number_info" /> - + + + + + + + + + + + + - + - - - - - - - - - - - + + + + + + + - - - + - - - + + + + - - - - - - - - - - - - - \ No newline at end of file + diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index d44c0246..217e9cd2 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,53 +1,10 @@ - - - - - - - -

Wersja 4.0, 2019-jeszcze-nie-wiem-kiedy

+

Wersja 4.4.1, 2020-09-03

    -
  • UWAGA. To jest wersja in-development. Wiele funkcji może nie działać prawidłowo (lub wcale), co oznacza tylko że nie zostały jeszcze przeniesione - z wersji 3.x. Proszę o cierpliwość oraz nie udostępnianie tej wersji nikomu.
  • -
  • Bardzo dużo zmian
  • +
  • Poprawione komunikaty o aktualizacjach aplikacji.
  • +
  • Mobidziennik: poprawione wyświetlanie przedmiotu w planie lekcji.
  • +
  • Mobidziennik: naprawiony moduł frekwencji.
- - - - \ No newline at end of file +
+
+Dzięki za korzystanie ze Szkolnego!
+© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020 diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..e8096e62 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,44 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + szkolny-signing + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + szkolny-signing.cpp) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +#[[find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log )]] + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + szkolny-signing + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) \ No newline at end of file diff --git a/app/src/main/cpp/aes.c b/app/src/main/cpp/aes.c new file mode 100644 index 00000000..c67bc03f --- /dev/null +++ b/app/src/main/cpp/aes.c @@ -0,0 +1,520 @@ +#include +#include +#include "aes.h" + +#include + +#define airport(x) (((x) << 8) | ((x) >> 24)) + +#define TRUE 1 +#define FALSE 0 + +static const toys wtf[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +static const toys help_me[256][6] = { + {0x00,0x00,0x00,0x00,0x00,0x00},{0x02,0x03,0x09,0x0b,0x0d,0x0e}, + {0x04,0x06,0x12,0x16,0x1a,0x1c},{0x06,0x05,0x1b,0x1d,0x17,0x12}, + {0x08,0x0c,0x24,0x2c,0x34,0x38},{0x0a,0x0f,0x2d,0x27,0x39,0x36}, + {0x0c,0x0a,0x36,0x3a,0x2e,0x24},{0x0e,0x09,0x3f,0x31,0x23,0x2a}, + {0x10,0x18,0x48,0x58,0x68,0x70},{0x12,0x1b,0x41,0x53,0x65,0x7e}, + {0x14,0x1e,0x5a,0x4e,0x72,0x6c},{0x16,0x1d,0x53,0x45,0x7f,0x62}, + {0x18,0x14,0x6c,0x74,0x5c,0x48},{0x1a,0x17,0x65,0x7f,0x51,0x46}, + {0x1c,0x12,0x7e,0x62,0x46,0x54},{0x1e,0x11,0x77,0x69,0x4b,0x5a}, + {0x20,0x30,0x90,0xb0,0xd0,0xe0},{0x22,0x33,0x99,0xbb,0xdd,0xee}, + {0x24,0x36,0x82,0xa6,0xca,0xfc},{0x26,0x35,0x8b,0xad,0xc7,0xf2}, + {0x28,0x3c,0xb4,0x9c,0xe4,0xd8},{0x2a,0x3f,0xbd,0x97,0xe9,0xd6}, + {0x2c,0x3a,0xa6,0x8a,0xfe,0xc4},{0x2e,0x39,0xaf,0x81,0xf3,0xca}, + {0x30,0x28,0xd8,0xe8,0xb8,0x90},{0x32,0x2b,0xd1,0xe3,0xb5,0x9e}, + {0x34,0x2e,0xca,0xfe,0xa2,0x8c},{0x36,0x2d,0xc3,0xf5,0xaf,0x82}, + {0x38,0x24,0xfc,0xc4,0x8c,0xa8},{0x3a,0x27,0xf5,0xcf,0x81,0xa6}, + {0x3c,0x22,0xee,0xd2,0x96,0xb4},{0x3e,0x21,0xe7,0xd9,0x9b,0xba}, + {0x40,0x60,0x3b,0x7b,0xbb,0xdb},{0x42,0x63,0x32,0x70,0xb6,0xd5}, + {0x44,0x66,0x29,0x6d,0xa1,0xc7},{0x46,0x65,0x20,0x66,0xac,0xc9}, + {0x48,0x6c,0x1f,0x57,0x8f,0xe3},{0x4a,0x6f,0x16,0x5c,0x82,0xed}, + {0x4c,0x6a,0x0d,0x41,0x95,0xff},{0x4e,0x69,0x04,0x4a,0x98,0xf1}, + {0x50,0x78,0x73,0x23,0xd3,0xab},{0x52,0x7b,0x7a,0x28,0xde,0xa5}, + {0x54,0x7e,0x61,0x35,0xc9,0xb7},{0x56,0x7d,0x68,0x3e,0xc4,0xb9}, + {0x58,0x74,0x57,0x0f,0xe7,0x93},{0x5a,0x77,0x5e,0x04,0xea,0x9d}, + {0x5c,0x72,0x45,0x19,0xfd,0x8f},{0x5e,0x71,0x4c,0x12,0xf0,0x81}, + {0x60,0x50,0xab,0xcb,0x6b,0x3b},{0x62,0x53,0xa2,0xc0,0x66,0x35}, + {0x64,0x56,0xb9,0xdd,0x71,0x27},{0x66,0x55,0xb0,0xd6,0x7c,0x29}, + {0x68,0x5c,0x8f,0xe7,0x5f,0x03},{0x6a,0x5f,0x86,0xec,0x52,0x0d}, + {0x6c,0x5a,0x9d,0xf1,0x45,0x1f},{0x6e,0x59,0x94,0xfa,0x48,0x11}, + {0x70,0x48,0xe3,0x93,0x03,0x4b},{0x72,0x4b,0xea,0x98,0x0e,0x45}, + {0x74,0x4e,0xf1,0x85,0x19,0x57},{0x76,0x4d,0xf8,0x8e,0x14,0x59}, + {0x78,0x44,0xc7,0xbf,0x37,0x73},{0x7a,0x47,0xce,0xb4,0x3a,0x7d}, + {0x7c,0x42,0xd5,0xa9,0x2d,0x6f},{0x7e,0x41,0xdc,0xa2,0x20,0x61}, + {0x80,0xc0,0x76,0xf6,0x6d,0xad},{0x82,0xc3,0x7f,0xfd,0x60,0xa3}, + {0x84,0xc6,0x64,0xe0,0x77,0xb1},{0x86,0xc5,0x6d,0xeb,0x7a,0xbf}, + {0x88,0xcc,0x52,0xda,0x59,0x95},{0x8a,0xcf,0x5b,0xd1,0x54,0x9b}, + {0x8c,0xca,0x40,0xcc,0x43,0x89},{0x8e,0xc9,0x49,0xc7,0x4e,0x87}, + {0x90,0xd8,0x3e,0xae,0x05,0xdd},{0x92,0xdb,0x37,0xa5,0x08,0xd3}, + {0x94,0xde,0x2c,0xb8,0x1f,0xc1},{0x96,0xdd,0x25,0xb3,0x12,0xcf}, + {0x98,0xd4,0x1a,0x82,0x31,0xe5},{0x9a,0xd7,0x13,0x89,0x3c,0xeb}, + {0x9c,0xd2,0x08,0x94,0x2b,0xf9},{0x9e,0xd1,0x01,0x9f,0x26,0xf7}, + {0xa0,0xf0,0xe6,0x46,0xbd,0x4d},{0xa2,0xf3,0xef,0x4d,0xb0,0x43}, + {0xa4,0xf6,0xf4,0x50,0xa7,0x51},{0xa6,0xf5,0xfd,0x5b,0xaa,0x5f}, + {0xa8,0xfc,0xc2,0x6a,0x89,0x75},{0xaa,0xff,0xcb,0x61,0x84,0x7b}, + {0xac,0xfa,0xd0,0x7c,0x93,0x69},{0xae,0xf9,0xd9,0x77,0x9e,0x67}, + {0xb0,0xe8,0xae,0x1e,0xd5,0x3d},{0xb2,0xeb,0xa7,0x15,0xd8,0x33}, + {0xb4,0xee,0xbc,0x08,0xcf,0x21},{0xb6,0xed,0xb5,0x03,0xc2,0x2f}, + {0xb8,0xe4,0x8a,0x32,0xe1,0x05},{0xba,0xe7,0x83,0x39,0xec,0x0b}, + {0xbc,0xe2,0x98,0x24,0xfb,0x19},{0xbe,0xe1,0x91,0x2f,0xf6,0x17}, + {0xc0,0xa0,0x4d,0x8d,0xd6,0x76},{0xc2,0xa3,0x44,0x86,0xdb,0x78}, + {0xc4,0xa6,0x5f,0x9b,0xcc,0x6a},{0xc6,0xa5,0x56,0x90,0xc1,0x64}, + {0xc8,0xac,0x69,0xa1,0xe2,0x4e},{0xca,0xaf,0x60,0xaa,0xef,0x40}, + {0xcc,0xaa,0x7b,0xb7,0xf8,0x52},{0xce,0xa9,0x72,0xbc,0xf5,0x5c}, + {0xd0,0xb8,0x05,0xd5,0xbe,0x06},{0xd2,0xbb,0x0c,0xde,0xb3,0x08}, + {0xd4,0xbe,0x17,0xc3,0xa4,0x1a},{0xd6,0xbd,0x1e,0xc8,0xa9,0x14}, + {0xd8,0xb4,0x21,0xf9,0x8a,0x3e},{0xda,0xb7,0x28,0xf2,0x87,0x30}, + {0xdc,0xb2,0x33,0xef,0x90,0x22},{0xde,0xb1,0x3a,0xe4,0x9d,0x2c}, + {0xe0,0x90,0xdd,0x3d,0x06,0x96},{0xe2,0x93,0xd4,0x36,0x0b,0x98}, + {0xe4,0x96,0xcf,0x2b,0x1c,0x8a},{0xe6,0x95,0xc6,0x20,0x11,0x84}, + {0xe8,0x9c,0xf9,0x11,0x32,0xae},{0xea,0x9f,0xf0,0x1a,0x3f,0xa0}, + {0xec,0x9a,0xeb,0x07,0x28,0xb2},{0xee,0x99,0xe2,0x0c,0x25,0xbc}, + {0xf0,0x88,0x95,0x65,0x6e,0xe6},{0xf2,0x8b,0x9c,0x6e,0x63,0xe8}, + {0xf4,0x8e,0x87,0x73,0x74,0xfa},{0xf6,0x8d,0x8e,0x78,0x79,0xf4}, + {0xf8,0x84,0xb1,0x49,0x5a,0xde},{0xfa,0x87,0xb8,0x42,0x57,0xd0}, + {0xfc,0x82,0xa3,0x5f,0x40,0xc2},{0xfe,0x81,0xaa,0x54,0x4d,0xcc}, + {0x1b,0x9b,0xec,0xf7,0xda,0x41},{0x19,0x98,0xe5,0xfc,0xd7,0x4f}, + {0x1f,0x9d,0xfe,0xe1,0xc0,0x5d},{0x1d,0x9e,0xf7,0xea,0xcd,0x53}, + {0x13,0x97,0xc8,0xdb,0xee,0x79},{0x11,0x94,0xc1,0xd0,0xe3,0x77}, + {0x17,0x91,0xda,0xcd,0xf4,0x65},{0x15,0x92,0xd3,0xc6,0xf9,0x6b}, + {0x0b,0x83,0xa4,0xaf,0xb2,0x31},{0x09,0x80,0xad,0xa4,0xbf,0x3f}, + {0x0f,0x85,0xb6,0xb9,0xa8,0x2d},{0x0d,0x86,0xbf,0xb2,0xa5,0x23}, + {0x03,0x8f,0x80,0x83,0x86,0x09},{0x01,0x8c,0x89,0x88,0x8b,0x07}, + {0x07,0x89,0x92,0x95,0x9c,0x15},{0x05,0x8a,0x9b,0x9e,0x91,0x1b}, + {0x3b,0xab,0x7c,0x47,0x0a,0xa1},{0x39,0xa8,0x75,0x4c,0x07,0xaf}, + {0x3f,0xad,0x6e,0x51,0x10,0xbd},{0x3d,0xae,0x67,0x5a,0x1d,0xb3}, + {0x33,0xa7,0x58,0x6b,0x3e,0x99},{0x31,0xa4,0x51,0x60,0x33,0x97}, + {0x37,0xa1,0x4a,0x7d,0x24,0x85},{0x35,0xa2,0x43,0x76,0x29,0x8b}, + {0x2b,0xb3,0x34,0x1f,0x62,0xd1},{0x29,0xb0,0x3d,0x14,0x6f,0xdf}, + {0x2f,0xb5,0x26,0x09,0x78,0xcd},{0x2d,0xb6,0x2f,0x02,0x75,0xc3}, + {0x23,0xbf,0x10,0x33,0x56,0xe9},{0x21,0xbc,0x19,0x38,0x5b,0xe7}, + {0x27,0xb9,0x02,0x25,0x4c,0xf5},{0x25,0xba,0x0b,0x2e,0x41,0xfb}, + {0x5b,0xfb,0xd7,0x8c,0x61,0x9a},{0x59,0xf8,0xde,0x87,0x6c,0x94}, + {0x5f,0xfd,0xc5,0x9a,0x7b,0x86},{0x5d,0xfe,0xcc,0x91,0x76,0x88}, + {0x53,0xf7,0xf3,0xa0,0x55,0xa2},{0x51,0xf4,0xfa,0xab,0x58,0xac}, + {0x57,0xf1,0xe1,0xb6,0x4f,0xbe},{0x55,0xf2,0xe8,0xbd,0x42,0xb0}, + {0x4b,0xe3,0x9f,0xd4,0x09,0xea},{0x49,0xe0,0x96,0xdf,0x04,0xe4}, + {0x4f,0xe5,0x8d,0xc2,0x13,0xf6},{0x4d,0xe6,0x84,0xc9,0x1e,0xf8}, + {0x43,0xef,0xbb,0xf8,0x3d,0xd2},{0x41,0xec,0xb2,0xf3,0x30,0xdc}, + {0x47,0xe9,0xa9,0xee,0x27,0xce},{0x45,0xea,0xa0,0xe5,0x2a,0xc0}, + {0x7b,0xcb,0x47,0x3c,0xb1,0x7a},{0x79,0xc8,0x4e,0x37,0xbc,0x74}, + {0x7f,0xcd,0x55,0x2a,0xab,0x66},{0x7d,0xce,0x5c,0x21,0xa6,0x68}, + {0x73,0xc7,0x63,0x10,0x85,0x42},{0x71,0xc4,0x6a,0x1b,0x88,0x4c}, + {0x77,0xc1,0x71,0x06,0x9f,0x5e},{0x75,0xc2,0x78,0x0d,0x92,0x50}, + {0x6b,0xd3,0x0f,0x64,0xd9,0x0a},{0x69,0xd0,0x06,0x6f,0xd4,0x04}, + {0x6f,0xd5,0x1d,0x72,0xc3,0x16},{0x6d,0xd6,0x14,0x79,0xce,0x18}, + {0x63,0xdf,0x2b,0x48,0xed,0x32},{0x61,0xdc,0x22,0x43,0xe0,0x3c}, + {0x67,0xd9,0x39,0x5e,0xf7,0x2e},{0x65,0xda,0x30,0x55,0xfa,0x20}, + {0x9b,0x5b,0x9a,0x01,0xb7,0xec},{0x99,0x58,0x93,0x0a,0xba,0xe2}, + {0x9f,0x5d,0x88,0x17,0xad,0xf0},{0x9d,0x5e,0x81,0x1c,0xa0,0xfe}, + {0x93,0x57,0xbe,0x2d,0x83,0xd4},{0x91,0x54,0xb7,0x26,0x8e,0xda}, + {0x97,0x51,0xac,0x3b,0x99,0xc8},{0x95,0x52,0xa5,0x30,0x94,0xc6}, + {0x8b,0x43,0xd2,0x59,0xdf,0x9c},{0x89,0x40,0xdb,0x52,0xd2,0x92}, + {0x8f,0x45,0xc0,0x4f,0xc5,0x80},{0x8d,0x46,0xc9,0x44,0xc8,0x8e}, + {0x83,0x4f,0xf6,0x75,0xeb,0xa4},{0x81,0x4c,0xff,0x7e,0xe6,0xaa}, + {0x87,0x49,0xe4,0x63,0xf1,0xb8},{0x85,0x4a,0xed,0x68,0xfc,0xb6}, + {0xbb,0x6b,0x0a,0xb1,0x67,0x0c},{0xb9,0x68,0x03,0xba,0x6a,0x02}, + {0xbf,0x6d,0x18,0xa7,0x7d,0x10},{0xbd,0x6e,0x11,0xac,0x70,0x1e}, + {0xb3,0x67,0x2e,0x9d,0x53,0x34},{0xb1,0x64,0x27,0x96,0x5e,0x3a}, + {0xb7,0x61,0x3c,0x8b,0x49,0x28},{0xb5,0x62,0x35,0x80,0x44,0x26}, + {0xab,0x73,0x42,0xe9,0x0f,0x7c},{0xa9,0x70,0x4b,0xe2,0x02,0x72}, + {0xaf,0x75,0x50,0xff,0x15,0x60},{0xad,0x76,0x59,0xf4,0x18,0x6e}, + {0xa3,0x7f,0x66,0xc5,0x3b,0x44},{0xa1,0x7c,0x6f,0xce,0x36,0x4a}, + {0xa7,0x79,0x74,0xd3,0x21,0x58},{0xa5,0x7a,0x7d,0xd8,0x2c,0x56}, + {0xdb,0x3b,0xa1,0x7a,0x0c,0x37},{0xd9,0x38,0xa8,0x71,0x01,0x39}, + {0xdf,0x3d,0xb3,0x6c,0x16,0x2b},{0xdd,0x3e,0xba,0x67,0x1b,0x25}, + {0xd3,0x37,0x85,0x56,0x38,0x0f},{0xd1,0x34,0x8c,0x5d,0x35,0x01}, + {0xd7,0x31,0x97,0x40,0x22,0x13},{0xd5,0x32,0x9e,0x4b,0x2f,0x1d}, + {0xcb,0x23,0xe9,0x22,0x64,0x47},{0xc9,0x20,0xe0,0x29,0x69,0x49}, + {0xcf,0x25,0xfb,0x34,0x7e,0x5b},{0xcd,0x26,0xf2,0x3f,0x73,0x55}, + {0xc3,0x2f,0xcd,0x0e,0x50,0x7f},{0xc1,0x2c,0xc4,0x05,0x5d,0x71}, + {0xc7,0x29,0xdf,0x18,0x4a,0x63},{0xc5,0x2a,0xd6,0x13,0x47,0x6d}, + {0xfb,0x0b,0x31,0xca,0xdc,0xd7},{0xf9,0x08,0x38,0xc1,0xd1,0xd9}, + {0xff,0x0d,0x23,0xdc,0xc6,0xcb},{0xfd,0x0e,0x2a,0xd7,0xcb,0xc5}, + {0xf3,0x07,0x15,0xe6,0xe8,0xef},{0xf1,0x04,0x1c,0xed,0xe5,0xe1}, + {0xf7,0x01,0x07,0xf0,0xf2,0xf3},{0xf5,0x02,0x0e,0xfb,0xff,0xfd}, + {0xeb,0x13,0x79,0x92,0xb4,0xa7},{0xe9,0x10,0x70,0x99,0xb9,0xa9}, + {0xef,0x15,0x6b,0x84,0xae,0xbb},{0xed,0x16,0x62,0x8f,0xa3,0xb5}, + {0xe3,0x1f,0x5d,0xbe,0x80,0x9f},{0xe1,0x1c,0x54,0xb5,0x8d,0x91}, + {0xe7,0x19,0x4f,0xa8,0x9a,0x83},{0xe5,0x1a,0x46,0xa3,0x97,0x8d} +}; + +void throat(const toys *decide, toys *selection, size_t plane) +{ + size_t idx; + + for (idx = 0; idx < plane; idx++) + selection[idx] ^= decide[idx]; +} + +int death(const toys *squirrel, size_t dear, toys *awful, const arrest *wrong, int magic, const toys *fit) +{ + toys supermarket[calculator], software[calculator], bookstore[calculator]; + int brainwash, jellybean; + + if (dear % calculator != 0) + return(FALSE); + + brainwash = dear / calculator; + + memcpy(bookstore, fit, calculator); + + for (jellybean = 0; jellybean < brainwash; jellybean++) { + memcpy(supermarket, &squirrel[jellybean * calculator], calculator); + throat(bookstore, supermarket, calculator); + punishment(supermarket, software, wrong, magic); + memcpy(&awful[jellybean * calculator], software, calculator); + memcpy(bookstore, software, calculator); + } + + return(TRUE); +} + +arrest please(arrest lol) +{ + unsigned int apps; + + apps = (int)wtf[(lol >> 4) & 0x0000000F][lol & 0x0000000F]; + apps += (int)wtf[(lol >> 12) & 0x0000000F][(lol >> 8) & 0x0000000F] << 8; + apps += (int)wtf[(lol >> 20) & 0x0000000F][(lol >> 16) & 0x0000000F] << 16; + apps += (int)wtf[(lol >> 28) & 0x0000000F][(lol >> 24) & 0x0000000F] << 24; + return(apps); +} + +void stoprightnow(const toys *fuckoff, arrest *waste, int roadtrip) +{ + int tour=4,cause,lively,reply; + arrest nope,desert[]={0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, + 0x40000000, 0x80000000, 0x1b000000, 0x36000000, 0x6c000000, 0xd8000000, + 0xab000000, 0x4d000000, 0x9a000000}; + + switch (roadtrip) { + case 128: cause = 10; lively = 4; break; + case 192: cause = 12; lively = 6; break; + case 256: cause = 14; lively = 8; break; + default: return; + } + + for (reply=0; reply < lively; ++reply) { + waste[reply] = ((fuckoff[4 * reply]) << 24) | ((fuckoff[4 * reply + 1]) << 16) | + ((fuckoff[4 * reply + 2]) << 8) | ((fuckoff[4 * reply + 3])); + } + + for (reply = lively; reply < tour * (cause + 1); ++reply) { + nope = waste[reply - 1]; + if ((reply % lively) == 0) + nope = please(airport(nope)) ^ desert[(reply - 1) / lively]; + else if (lively > 6 && (reply % lively) == 4) + nope = please(nope); + waste[reply] = waste[reply - lively] ^ nope; + } +} + +void hot(toys rare[][4], const arrest powerful[]) +{ + toys interfere[4]; + + // memcpy(interfere,&powerful[idx],4); // Not accurate for big endian machines + // Subkey 1 + interfere[0] = powerful[0] >> 24; + interfere[1] = powerful[0] >> 16; + interfere[2] = powerful[0] >> 8; + interfere[3] = powerful[0]; + rare[0][0] ^= interfere[0]; + rare[1][0] ^= interfere[1]; + rare[2][0] ^= interfere[2]; + rare[3][0] ^= interfere[3]; + // Subkey 2 + interfere[0] = powerful[1] >> 24; + interfere[1] = powerful[1] >> 16; + interfere[2] = powerful[1] >> 8; + interfere[3] = powerful[1]; + rare[0][1] ^= interfere[0]; + rare[1][1] ^= interfere[1]; + rare[2][1] ^= interfere[2]; + rare[3][1] ^= interfere[3]; + // Subkey 3 + interfere[0] = powerful[2] >> 24; + interfere[1] = powerful[2] >> 16; + interfere[2] = powerful[2] >> 8; + interfere[3] = powerful[2]; + rare[0][2] ^= interfere[0]; + rare[1][2] ^= interfere[1]; + rare[2][2] ^= interfere[2]; + rare[3][2] ^= interfere[3]; + // Subkey 4 + interfere[0] = powerful[3] >> 24; + interfere[1] = powerful[3] >> 16; + interfere[2] = powerful[3] >> 8; + interfere[3] = powerful[3]; + rare[0][3] ^= interfere[0]; + rare[1][3] ^= interfere[1]; + rare[2][3] ^= interfere[2]; + rare[3][3] ^= interfere[3]; +} + +void numerous(toys vigorous[][4]) +{ + vigorous[0][0] = wtf[vigorous[0][0] >> 4][vigorous[0][0] & 0x0F]; + vigorous[0][1] = wtf[vigorous[0][1] >> 4][vigorous[0][1] & 0x0F]; + vigorous[0][2] = wtf[vigorous[0][2] >> 4][vigorous[0][2] & 0x0F]; + vigorous[0][3] = wtf[vigorous[0][3] >> 4][vigorous[0][3] & 0x0F]; + vigorous[1][0] = wtf[vigorous[1][0] >> 4][vigorous[1][0] & 0x0F]; + vigorous[1][1] = wtf[vigorous[1][1] >> 4][vigorous[1][1] & 0x0F]; + vigorous[1][2] = wtf[vigorous[1][2] >> 4][vigorous[1][2] & 0x0F]; + vigorous[1][3] = wtf[vigorous[1][3] >> 4][vigorous[1][3] & 0x0F]; + vigorous[2][0] = wtf[vigorous[2][0] >> 4][vigorous[2][0] & 0x0F]; + vigorous[2][1] = wtf[vigorous[2][1] >> 4][vigorous[2][1] & 0x0F]; + vigorous[2][2] = wtf[vigorous[2][2] >> 4][vigorous[2][2] & 0x0F]; + vigorous[2][3] = wtf[vigorous[2][3] >> 4][vigorous[2][3] & 0x0F]; + vigorous[3][0] = wtf[vigorous[3][0] >> 4][vigorous[3][0] & 0x0F]; + vigorous[3][1] = wtf[vigorous[3][1] >> 4][vigorous[3][1] & 0x0F]; + vigorous[3][2] = wtf[vigorous[3][2] >> 4][vigorous[3][2] & 0x0F]; + vigorous[3][3] = wtf[vigorous[3][3] >> 4][vigorous[3][3] & 0x0F]; +} + +void crowded(toys chalk[][4]) +{ + int t; + + // Shift left by 1 + t = chalk[1][0]; + chalk[1][0] = chalk[1][1]; + chalk[1][1] = chalk[1][2]; + chalk[1][2] = chalk[1][3]; + chalk[1][3] = t; + // Shift left by 2 + t = chalk[2][0]; + chalk[2][0] = chalk[2][2]; + chalk[2][2] = t; + t = chalk[2][1]; + chalk[2][1] = chalk[2][3]; + chalk[2][3] = t; + // Shift left by 3 + t = chalk[3][0]; + chalk[3][0] = chalk[3][3]; + chalk[3][3] = chalk[3][2]; + chalk[3][2] = chalk[3][1]; + chalk[3][1] = t; +} + +void scale(toys oh_no[][4]) +{ + toys idk[4]; + + // Column 1 + idk[0] = oh_no[0][0]; + idk[1] = oh_no[1][0]; + idk[2] = oh_no[2][0]; + idk[3] = oh_no[3][0]; + oh_no[0][0] = help_me[idk[0]][0]; + oh_no[0][0] ^= help_me[idk[1]][1]; + oh_no[0][0] ^= idk[2]; + oh_no[0][0] ^= idk[3]; + oh_no[1][0] = idk[0]; + oh_no[1][0] ^= help_me[idk[1]][0]; + oh_no[1][0] ^= help_me[idk[2]][1]; + oh_no[1][0] ^= idk[3]; + oh_no[2][0] = idk[0]; + oh_no[2][0] ^= idk[1]; + oh_no[2][0] ^= help_me[idk[2]][0]; + oh_no[2][0] ^= help_me[idk[3]][1]; + oh_no[3][0] = help_me[idk[0]][1]; + oh_no[3][0] ^= idk[1]; + oh_no[3][0] ^= idk[2]; + oh_no[3][0] ^= help_me[idk[3]][0]; + // Column 2 + idk[0] = oh_no[0][1]; + idk[1] = oh_no[1][1]; + idk[2] = oh_no[2][1]; + idk[3] = oh_no[3][1]; + oh_no[0][1] = help_me[idk[0]][0]; + oh_no[0][1] ^= help_me[idk[1]][1]; + oh_no[0][1] ^= idk[2]; + oh_no[0][1] ^= idk[3]; + oh_no[1][1] = idk[0]; + oh_no[1][1] ^= help_me[idk[1]][0]; + oh_no[1][1] ^= help_me[idk[2]][1]; + oh_no[1][1] ^= idk[3]; + oh_no[2][1] = idk[0]; + oh_no[2][1] ^= idk[1]; + oh_no[2][1] ^= help_me[idk[2]][0]; + oh_no[2][1] ^= help_me[idk[3]][1]; + oh_no[3][1] = help_me[idk[0]][1]; + oh_no[3][1] ^= idk[1]; + oh_no[3][1] ^= idk[2]; + oh_no[3][1] ^= help_me[idk[3]][0]; + // Column 3 + idk[0] = oh_no[0][2]; + idk[1] = oh_no[1][2]; + idk[2] = oh_no[2][2]; + idk[3] = oh_no[3][2]; + oh_no[0][2] = help_me[idk[0]][0]; + oh_no[0][2] ^= help_me[idk[1]][1]; + oh_no[0][2] ^= idk[2]; + oh_no[0][2] ^= idk[3]; + oh_no[1][2] = idk[0]; + oh_no[1][2] ^= help_me[idk[1]][0]; + oh_no[1][2] ^= help_me[idk[2]][1]; + oh_no[1][2] ^= idk[3]; + oh_no[2][2] = idk[0]; + oh_no[2][2] ^= idk[1]; + oh_no[2][2] ^= help_me[idk[2]][0]; + oh_no[2][2] ^= help_me[idk[3]][1]; + oh_no[3][2] = help_me[idk[0]][1]; + oh_no[3][2] ^= idk[1]; + oh_no[3][2] ^= idk[2]; + oh_no[3][2] ^= help_me[idk[3]][0]; + // Column 4 + idk[0] = oh_no[0][3]; + idk[1] = oh_no[1][3]; + idk[2] = oh_no[2][3]; + idk[3] = oh_no[3][3]; + oh_no[0][3] = help_me[idk[0]][0]; + oh_no[0][3] ^= help_me[idk[1]][1]; + oh_no[0][3] ^= idk[2]; + oh_no[0][3] ^= idk[3]; + oh_no[1][3] = idk[0]; + oh_no[1][3] ^= help_me[idk[1]][0]; + oh_no[1][3] ^= help_me[idk[2]][1]; + oh_no[1][3] ^= idk[3]; + oh_no[2][3] = idk[0]; + oh_no[2][3] ^= idk[1]; + oh_no[2][3] ^= help_me[idk[2]][0]; + oh_no[2][3] ^= help_me[idk[3]][1]; + oh_no[3][3] = help_me[idk[0]][1]; + oh_no[3][3] ^= idk[1]; + oh_no[3][3] ^= idk[2]; + oh_no[3][3] ^= help_me[idk[3]][0]; +} + +void punishment(const toys friends[], toys number[], const arrest wish[], int hang) +{ + toys burst[4][4]; + + burst[0][0] = friends[0]; + burst[1][0] = friends[1]; + burst[2][0] = friends[2]; + burst[3][0] = friends[3]; + burst[0][1] = friends[4]; + burst[1][1] = friends[5]; + burst[2][1] = friends[6]; + burst[3][1] = friends[7]; + burst[0][2] = friends[8]; + burst[1][2] = friends[9]; + burst[2][2] = friends[10]; + burst[3][2] = friends[11]; + burst[0][3] = friends[12]; + burst[1][3] = friends[13]; + burst[2][3] = friends[14]; + burst[3][3] = friends[15]; + + hot(burst, &wish[0]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[4]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[8]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[12]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[16]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[20]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[24]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[28]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[32]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[36]); + if (hang != 128) { + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[40]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[44]); + if (hang != 192) { + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[48]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[52]); + numerous(burst); + crowded(burst); + hot(burst, &wish[56]); + } + else { + numerous(burst); + crowded(burst); + hot(burst, &wish[48]); + } + } + else { + numerous(burst); + crowded(burst); + hot(burst, &wish[40]); + } + + number[0] = burst[0][0]; + number[1] = burst[1][0]; + number[2] = burst[2][0]; + number[3] = burst[3][0]; + number[4] = burst[0][1]; + number[5] = burst[1][1]; + number[6] = burst[2][1]; + number[7] = burst[3][1]; + number[8] = burst[0][2]; + number[9] = burst[1][2]; + number[10] = burst[2][2]; + number[11] = burst[3][2]; + number[12] = burst[0][3]; + number[13] = burst[1][3]; + number[14] = burst[2][3]; + number[15] = burst[3][3]; +} + diff --git a/app/src/main/cpp/aes.h b/app/src/main/cpp/aes.h new file mode 100644 index 00000000..55856f40 --- /dev/null +++ b/app/src/main/cpp/aes.h @@ -0,0 +1,27 @@ +#ifndef AES_H +#define AES_H + +#include + +#define calculator 16 + +typedef unsigned char toys; +typedef unsigned int arrest; + +void stoprightnow(const toys *fuckoff, + arrest *waste, + int roadtrip); + +void punishment(const toys *friends, + toys *number, + const arrest *wish, + int hang); + +int death(const toys *squirrel, + size_t dear, + toys *awful, + const arrest *wrong, + int magic, + const toys *fit); + +#endif // AES_H diff --git a/app/src/main/cpp/base64.cpp b/app/src/main/cpp/base64.cpp new file mode 100644 index 00000000..5170d8b4 --- /dev/null +++ b/app/src/main/cpp/base64.cpp @@ -0,0 +1,55 @@ +#include "jni.h" +#include +#include + +const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +char *kill_me(const char *coil, size_t room) { + int canvas = 0; + size_t irritating; + int exchange = 0; + char *untidy = NULL; + char *excellent = NULL; + int sincere = 0; + char development[4]; + int trade = 0; + irritating = room / 3; + exchange = room % 3; + if (exchange > 0) { + irritating += 1; + } + irritating = irritating * 4 + 1; + untidy = (char *) malloc(irritating); + + if (untidy == NULL) { + exit(0); + } + memset(untidy, 0, irritating); + excellent = untidy; + while (sincere < room) { + exchange = 0; + canvas = 0; + memset(development, '\0', 4); + while (exchange < 3) { + if (sincere >= room) { + break; + } + canvas = ((canvas << 8) | (coil[sincere] & 0xFF)); + sincere++; + exchange++; + } + canvas = (canvas << ((3 - exchange) * 8)); + for (trade = 0; trade < 4; trade++) { + if (exchange < trade) { + development[trade] = 0x40; + } + else { + development[trade] = (canvas >> ((3 - trade) * 6)) & 0x3F; + } + *excellent = base[development[trade]]; + excellent++; + } + } + *excellent = '\0'; + return untidy; +} diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp new file mode 100644 index 00000000..4d19eb33 --- /dev/null +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -0,0 +1,78 @@ +#include +#include +#include "aes.c" +#include "aes.h" +#include "base64.cpp" + +#define overrated (2*1024*1024) +#define teeth 256 + +/*secret password - removed for source code publication*/ +static toys AES_IV[16] = { + 0x72, 0x4b, 0x61, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); + +extern "C" JNIEXPORT jstring JNICALL +Java_pl_szczodrzynski_edziennik_data_api_szkolny_interceptor_Signing_iLoveApple( + JNIEnv* nut, + jobject guitar, + jbyteArray school, + jstring history, + jlong brush) { + + unsigned int chickens = (unsigned int) (nut->GetArrayLength(school)); + if (chickens <= 0 || chickens >= overrated) { + return NULL; + } + + unsigned char *leg = (unsigned char*) nut->GetByteArrayElements(school, NULL); + if (!leg) { + return NULL; + } + + jclass partner = nut->FindClass("pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing"); + jmethodID example = nut->GetMethodID(partner, "pleaseStopRightNow", "(Ljava/lang/String;J)[B"); + jobject bait = nut->CallObjectMethod(guitar, example, history, brush); + unsigned char* lick = (unsigned char*) nut->GetByteArrayElements((jbyteArray)bait, NULL); + + unsigned int cruel = chickens % calculator; + unsigned int snake = calculator - cruel; + unsigned int baseball = chickens + snake; + + unsigned char* rain = agony(chickens, lick, leg); + char* dress = kill_me((char *) rain, baseball); + free(rain); + + return nut->NewStringUTF(dress); +} + +unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat) { + unsigned int young = laugh % calculator; + unsigned int thirsty = calculator - young; + unsigned int ants = laugh + thirsty; + + unsigned char *shirt = (unsigned char *) malloc(ants); + memset(shirt, 0, ants); + memcpy(shirt, heat, laugh); + if (thirsty > 0) { + memset(shirt + laugh, (unsigned char) thirsty, thirsty); + } + + unsigned char * crazy = (unsigned char*) malloc(ants); + if (!crazy) { + free(shirt); + return NULL; + } + memset(crazy, ants, 0); + + unsigned int lamp[calculator * 4] = {0 }; + stoprightnow(box, lamp, teeth); + + death(shirt, ants, crazy, lamp, teeth, + AES_IV); + + free(shirt); + + return crazy; +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.java b/app/src/main/java/pl/szczodrzynski/edziennik/App.java deleted file mode 100644 index b975aff9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.java +++ /dev/null @@ -1,717 +0,0 @@ -package pl.szczodrzynski.edziennik; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.content.pm.Signature; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.Icon; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; -import android.util.Base64; -import android.util.Log; -import android.util.Pair; - -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.work.Configuration; - -import com.chuckerteam.chucker.api.ChuckerCollector; -import com.chuckerteam.chucker.api.ChuckerInterceptor; -import com.chuckerteam.chucker.api.RetentionManager; -import com.google.android.gms.security.ProviderInstaller; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import com.hypertrack.hyperlog.HyperLog; -import com.mikepenz.iconics.Iconics; -import com.mikepenz.iconics.IconicsColor; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.IconicsSize; -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont; - -import java.lang.reflect.Field; -import java.security.KeyStore; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import cat.ereza.customactivityoncrash.config.CaocConfig; -import im.wangchao.mhttp.MHttp; -import im.wangchao.mhttp.internal.cookie.PersistentCookieJar; -import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache; -import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor; -import me.leolin.shortcutbadger.ShortcutBadger; -import okhttp3.ConnectionSpec; -import okhttp3.OkHttpClient; -import okhttp3.TlsVersion; -import pl.szczodrzynski.edziennik.data.db.AppDb; -import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog; -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore; -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile; -import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull; -import pl.szczodrzynski.edziennik.network.NetworkUtils; -import pl.szczodrzynski.edziennik.network.TLSSocketFactory; -import pl.szczodrzynski.edziennik.sync.SyncWorker; -import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity; -import pl.szczodrzynski.edziennik.utils.DebugLogFormat; -import pl.szczodrzynski.edziennik.utils.PermissionChecker; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; -import pl.szczodrzynski.edziennik.utils.models.AppConfig; - -import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS; -import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK; -import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_VULCAN; - -public class App extends androidx.multidex.MultiDexApplication implements Configuration.Provider { - private static final String TAG = "App"; - public static int profileId = -1; - private Context mContext; - - @Override - public Configuration getWorkManagerConfiguration() { - return new Configuration.Builder() - .setMinimumLoggingLevel(Log.VERBOSE) - .build(); - } - - - public static final int REQUEST_TIMEOUT = 10 * 1000; - - // notifications - //public NotificationManager mNotificationManager; - //public final String NOTIFICATION_CHANNEL_ID_UPDATES = "4566"; - //public String NOTIFICATION_CHANNEL_NAME_UPDATES; - public Notifier notifier; - - public static final String APP_URL = "://edziennik.szczodrzynski.pl/app/"; - - public ShortcutManager shortcutManager; - - public PermissionChecker permissionChecker; - - public String signature = ""; - public String deviceId = ""; - - public AppDb db; - public void debugLog(String text) { - if (!devMode) - return; - db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text)); - } - public void debugLogAsync(String text) { - if (!devMode) - return; - AsyncTask.execute(() -> { - db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text)); - }); - } - - // network & APIs - public NetworkUtils networkUtils; - public PersistentCookieJar cookieJar; - public OkHttpClient http; - public OkHttpClient httpLazy; - - public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE - public AppConfig appConfig; // APPCONFIG: common for all profiles - //public AppProfile profile; // current profile - public JsonObject loginStore = null; - public SharedPreferences registerStore; // sharedPreferences for REGISTER - //public Register register; // REGISTER for current profile, read from registerStore - - public ProfileFull profile; - - // other stuff - public Gson gson; - public String requestScheme = "https"; - public boolean unreadBadgesAvailable = true; - - public static boolean devMode = false; - - public static final boolean UPDATES_ON_PLAY_STORE = true; - - @RequiresApi(api = Build.VERSION_CODES.M) - public Icon getDesktopIconFromIconics(IIcon icon) { - final IconicsDrawable drawable = new IconicsDrawable(mContext, icon) - .color(IconicsColor.colorInt(Color.WHITE)) - .size(IconicsSize.dp(48)) - .padding(IconicsSize.dp(8)) - .backgroundColor(IconicsColor.colorRes(R.color.colorPrimaryDark)) - .roundedCorners(IconicsSize.dp(10)); - //drawable.setStyle(Paint.Style.FILL); - final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return Icon.createWithBitmap(bitmap); - } - - @Override - public void onCreate() { - super.onCreate(); - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); - CaocConfig.Builder.create() - .backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM) //default: CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM - .enabled(true) //default: true - .showErrorDetails(true) //default: true - .showRestartButton(true) //default: true - .logErrorOnRestart(true) //default: true - .trackActivities(true) //default: false - .minTimeBetweenCrashesMs(2000) //default: 3000 - .errorDrawable(R.drawable.ic_rip) //default: bug image - .restartActivity(MainActivity.class) //default: null (your app's launch activity) - .errorActivity(CrashActivity.class) //default: null (default error activity) - //.eventListener(new YourCustomEventListener()) //default: null - .apply(); - mContext = this; - db = AppDb.getDatabase(this); - gson = new Gson(); - networkUtils = new NetworkUtils(this); - - Iconics.init(getApplicationContext()); - Iconics.registerFont(SzkolnyFont.INSTANCE); - - notifier = new Notifier(this); - permissionChecker = new PermissionChecker(mContext); - - deviceId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); - - cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this)); - - appSharedPrefs = getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE); - - loadConfig(); - - Themes.INSTANCE.setThemeInt(appConfig.appTheme); - - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); - for (Signature signature: packageInfo.signatures) { - byte[] signatureBytes = signature.toByteArray(); - MessageDigest md = MessageDigest.getInstance("SHA"); - md.update(signatureBytes); - this.signature = Base64.encodeToString(md.digest(), Base64.DEFAULT); - //Log.d(TAG, "Signature is "+this.signature); - } - } - catch (Exception e) { - e.printStackTrace(); - } - - if ("f054761fbdb6a238".equals(deviceId) || BuildConfig.DEBUG) { - devMode = true; - } - else if (appConfig.devModePassword != null) { - checkDevModePassword(); - } - - OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder() - .cache(null) - .followRedirects(true) - .followSslRedirects(true) - .retryOnConnectionFailure(true) - .cookieJar(cookieJar) - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(20, TimeUnit.SECONDS) - .readTimeout(40, TimeUnit.SECONDS); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - try { - try { - ProviderInstaller.installIfNeeded(this); - } catch (Exception e) { - Log.e("OkHttpTLSCompat", "Play Services not found or outdated"); - X509TrustManager x509TrustManager = null; - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) { - if (trustManager instanceof X509TrustManager) - x509TrustManager = (X509TrustManager) trustManager; - } - - SSLContext sc = SSLContext.getInstance("TLSv1.2"); - sc.init(null, null, null); - httpBuilder.sslSocketFactory(new TLSSocketFactory(sc.getSocketFactory()), x509TrustManager); - - ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_0) - .tlsVersions(TlsVersion.TLS_1_1) - .tlsVersions(TlsVersion.TLS_1_2) - .build(); - - List specs = new ArrayList<>(); - specs.add(cs); - specs.add(ConnectionSpec.COMPATIBLE_TLS); - specs.add(ConnectionSpec.CLEARTEXT); - - httpBuilder.connectionSpecs(specs); - } - - - } catch (Exception exc) { - Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc); - } - } - - if (App.devMode || BuildConfig.DEBUG) { - HyperLog.initialize(this); - HyperLog.setLogLevel(Log.VERBOSE); - HyperLog.setLogFormat(new DebugLogFormat(this)); - - ChuckerCollector chuckerCollector = new ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR); - ChuckerInterceptor chuckerInterceptor = new ChuckerInterceptor(this, chuckerCollector); - httpBuilder.addInterceptor(chuckerInterceptor); - } - - http = httpBuilder.build(); - httpLazy = http.newBuilder().followRedirects(false).followSslRedirects(false).build(); - - MHttp.instance() - .customOkHttpClient(http); - - //register = new Register(mContext); - - //profileLoadById(appSharedPrefs.getInt("current_profile_id", 1)); - - if (appConfig.registerSyncEnabled) { - SyncWorker.Companion.scheduleNext(this, false); - } - else { - SyncWorker.Companion.cancelNext(this); - } - - db.metadataDao().countUnseen().observeForever(count -> { - Log.d("MainActivity", "Overall unseen count changed"); - assert count != null; - if (unreadBadgesAvailable) { - unreadBadgesAvailable = ShortcutBadger.applyCount(this, count); - } - }); - - //new IonCookieManager(mContext); - - new Handler().post(() -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - shortcutManager = getSystemService(ShortcutManager.class); - - ShortcutInfo shortcutTimetable = new ShortcutInfo.Builder(mContext, "item_timetable") - .setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_timetable)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_timetable)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)) - .build(); - - ShortcutInfo shortcutAgenda = new ShortcutInfo.Builder(mContext, "item_agenda") - .setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_agenda)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_calendar)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA)) - .build(); - - ShortcutInfo shortcutGrades = new ShortcutInfo.Builder(mContext, "item_grades") - .setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_grades)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_numeric_5_box)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES)) - .build(); - - ShortcutInfo shortcutHomework = new ShortcutInfo.Builder(mContext, "item_homeworks") - .setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_homework)) - //.setIcon(getDesktopIconFromIconics(SzkolnyFont.Icon.szf_file_document_edit)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK)) - .build(); - - ShortcutInfo shortcutMessages = new ShortcutInfo.Builder(mContext, "item_messages") - .setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_messages)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_email)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES )) - .build(); - - shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutTimetable, shortcutAgenda, shortcutGrades, shortcutHomework, shortcutMessages)); - } - - if (appConfig.appInstalledTime == 0) { - try { - appConfig.appInstalledTime = getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime; - appConfig.appRateSnackbarTime = appConfig.appInstalledTime + 7 * 24 * 60 * 60 * 1000; - saveConfig("appInstalledTime", "appRateSnackbarTime"); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - - /*Task capabilityInfoTask = - Wearable.getCapabilityClient(this) - .getCapability("edziennik_wear_app", CapabilityClient.FILTER_REACHABLE); - capabilityInfoTask.addOnCompleteListener((task) -> { - if (task.isSuccessful()) { - CapabilityInfo capabilityInfo = task.getResult(); - assert capabilityInfo != null; - Set nodes; - nodes = capabilityInfo.getNodes(); - Log.d(TAG, "Nodes "+nodes); - - if (nodes.size() > 0) { - Wearable.getMessageClient(this).sendMessage( - nodes.toArray(new Node[]{})[0].getId(), "/ping", "Hello world".getBytes()); - } - } else { - Log.d(TAG, "Capability request failed to return any results."); - } - }); - - Wearable.getDataClient(this).addListener(dataEventBuffer -> { - Log.d(TAG, "onDataChanged(): " + dataEventBuffer); - - for (DataEvent event : dataEventBuffer) { - if (event.getType() == DataEvent.TYPE_CHANGED) { - String path = event.getDataItem().getUri().getPath(); - Log.d(TAG, "Data "+path+ " :: "+Arrays.toString(event.getDataItem().getData())); - } - } - });*/ - - FirebaseApp pushMobidziennikApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE") - .setApplicationId("1:747285019373:android:f6341bf7b158621d") - .build(), - "Mobidziennik2" - ); - - FirebaseApp pushLibrusApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o") - .setApplicationId("1:513056078587:android:1e29083b760af544") - .build(), - "Librus" - ); - - FirebaseApp pushVulcanApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA") - .setApplicationId("1:987828170337:android:ac97431a0a4578c3") - .build(), - "Vulcan" - ); - - try { - final long startTime = System.currentTimeMillis(); - FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for App is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()+". Time is "+(System.currentTimeMillis() - startTime)); - appConfig.fcmToken = instanceIdResult.getToken(); - }); - FirebaseInstanceId.getInstance(pushMobidziennikApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Mobidziennik is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - appConfig.fcmTokens.put(LOGIN_TYPE_MOBIDZIENNIK, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - }); - FirebaseInstanceId.getInstance(pushLibrusApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Librus is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - appConfig.fcmTokens.put(LOGIN_TYPE_LIBRUS, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - }); - FirebaseInstanceId.getInstance(pushVulcanApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Vulcan is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - Pair> pair = appConfig.fcmTokens.get(LOGIN_TYPE_VULCAN); - if (pair == null || pair.first == null || !pair.first.equals(instanceIdResult.getToken())) { - appConfig.fcmTokens.put(LOGIN_TYPE_VULCAN, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - } - }); - - - FirebaseMessaging.getInstance().subscribeToTopic(getPackageName()); - } - catch (IllegalStateException e) { - e.printStackTrace(); - } - }); - - } - - - public void loadConfig() - { - appConfig = new AppConfig(this); - - - if (appSharedPrefs.contains("config")) { - // remove old-format config, save the new one and empty the incorrectly-nulled config - appConfig = gson.fromJson(appSharedPrefs.getString("config", ""), AppConfig.class); - appSharedPrefs.edit().remove("config").apply(); - saveConfig(); - appConfig = new AppConfig(this); - } - - if (appSharedPrefs.contains("profiles")) { - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - /*List appProfileIds = gson.fromJson(appSharedPrefs.getString("profiles", ""), new TypeToken>(){}.getType()); - for (int id: appProfileIds) { - AppProfile appProfile = gson.fromJson(appSharedPrefs.getString("profile"+id, ""), AppProfile.class); - if (appProfile != null) { - appConfig.profiles.add(appProfile); - } - appSharedPrefsEditor.remove("profile"+id); - }*/ - appSharedPrefsEditor.remove("profiles"); - appSharedPrefsEditor.apply(); - //profilesSave(); - } - - - Map keys = appSharedPrefs.getAll(); - for (Map.Entry entry : keys.entrySet()) { - if (entry.getKey().startsWith("app.appConfig.")) { - String fieldName = entry.getKey().replace("app.appConfig.", ""); - - try { - Field field = AppConfig.class.getField(fieldName); - Object object; - try { - object = gson.fromJson(entry.getValue().toString(), field.getGenericType()); - } catch (JsonSyntaxException e) { - Log.d(TAG, "For field "+fieldName); - e.printStackTrace(); - object = entry.getValue().toString(); - } - if (object != null) - field.set(appConfig, object); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - appSharedPrefs.edit().remove("app.appConfig."+fieldName).apply(); - } - } - } - - /*if (appConfig.lastAppVersion > BuildConfig.VERSION_CODE) { - BootReceiver br = new BootReceiver(); - Intent i = new Intent(); - //i.putExtra("UserChecked", true); - br.onReceive(getContext(), i); - Toast.makeText(mContext, R.string.warning_older_version_running, Toast.LENGTH_LONG).show(); - //Toast.makeText(mContext, "Zaktualizuj aplikację.", Toast.LENGTH_LONG).show(); - //System.exit(0); - }*/ - - if (appConfig == null) { - appConfig = new AppConfig(this); - } - } - public void saveConfig() - { - try { - appConfig.savePending = false; - - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - - JsonObject appConfigJson = gson.toJsonTree(appConfig).getAsJsonObject(); - for (Map.Entry entry : appConfigJson.entrySet()) { - String jsonObj; - jsonObj = entry.getValue().toString(); - /*if (entry.getValue().isJsonObject()) { - jsonObj = entry.getValue().getAsJsonObject().toString(); - } - else if (entry.getValue().isJsonArray()) { - jsonObj = entry.getValue().getAsJsonArray().toString(); - } - else { - jsonObj = entry.getValue().toString(); - }*/ - appSharedPrefsEditor.putString("app.appConfig." + entry.getKey(), jsonObj); - } - - appSharedPrefsEditor.apply(); - } - catch (ConcurrentModificationException e) { - e.printStackTrace(); - } - //appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply(); - } - public void saveConfig(String ... fieldNames) - { - appConfig.savePending = false; - - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - - for (String fieldName: fieldNames) { - try { - Object object = AppConfig.class.getField(fieldName).get(appConfig); - appSharedPrefsEditor.putString("app.appConfig."+fieldName, gson.toJson(object)); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (ConcurrentModificationException e) { - e.printStackTrace(); - } - } - - appSharedPrefsEditor.apply(); - //appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply(); - } - - - - public void profileSaveAsync() { - AsyncTask.execute(() -> { - db.profileDao().add(profile); - }); - } - public void profileSaveAsync(Profile profile) { - AsyncTask.execute(() -> { - db.profileDao().add(profile); - }); - } - public void profileSaveFullAsync(ProfileFull profile) { - AsyncTask.execute(() -> { - profileSaveFull(profile); - }); - } - public void profileSaveFull(ProfileFull profileFull) { - db.profileDao().add(profileFull); - db.loginStoreDao().add(profileFull); - } - public void profileSaveFull(Profile profile, LoginStore loginStore) { - db.profileDao().add(profile); - db.loginStoreDao().add(loginStore); - } - - public ProfileFull profileGetOrNull(int id) { - return db.profileDao().getFullByIdNow(id); - } - - public void profileLoadById(int id) { - profileLoadById(id, false); - } - public void profileLoadById(int id, boolean loadedLast) { - //Log.d(TAG, "Loading ID "+id); - /*if (profile == null) { - profile = profileNew(); - AppDb.profileId = profile.id; - appSharedPrefs.edit().putInt("current_profile_id", profile.id).apply(); - return; - }*/ - if (profile == null || profile.getId() != id) { - profile = db.profileDao().getFullByIdNow(id); - /*if (profile == null) { - profileLoadById(id); - return; - }*/ - if (profile != null) { - MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1); - profileId = profile.getId(); - appSharedPrefs.edit().putInt("current_profile_id", profile.getId()).apply(); - } - else if (!loadedLast) { - profileLoadById(profileLastId(), true); - } - else { - profileId = -1; - } - } - } - - public void profileLoad(ProfileFull profile) { - MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1); - this.profile = profile; - profileId = profile.getId(); - } - - /*public void profileRemove(int id) - { - Profile profile = db.profileDao().getFullByIdNow(id); - - if (profile.id == profile.loginStoreId) { - // this profile is the owner of the login store - // we need to check if any other profile is using it - List transferProfileIds = db.profileDao().getIdsByLoginStoreIdNow(profile.loginStoreId); - if (transferProfileIds.size() == 1) { - // this login store is free of users, remove it along with the profile - db.loginStoreDao().remove(profile.loginStoreId); - // the current store is removed, we are ready to remove the profile - } - else if (transferProfileIds.size() > 1) { - transferProfileIds.remove(transferProfileIds.indexOf(profile.id)); - // someone is using the store - // we need to transfer it to the firstProfileId - db.loginStoreDao().changeId(profile.loginStoreId, transferProfileIds.get(0)); - db.profileDao().changeStoreId(profile.loginStoreId, transferProfileIds.get(0)); - // the current store is removed, we are ready to remove the profile - } - } - // else, the profile uses a store that it doesn't own - // leave the store and go on with removing - - Log.d(TAG, "Before removal: "+db.profileDao().getAllNow().toString()); - db.profileDao().remove(profile.id); - Log.d(TAG, "After removal: "+db.profileDao().getAllNow().toString()); - - *//*int newId = 1; - if (appConfig.profiles.size() > 0) { - newId = appConfig.profiles.get(appConfig.profiles.size() - 1).id; - } - Log.d(TAG, "New ID: "+newId); - //Toast.makeText(mContext, "selected new id "+newId, Toast.LENGTH_SHORT).show(); - profileLoadById(newId);*//* - }*/ - - public int profileFirstId() { - return db.profileDao().getFirstId(); - } - - public int profileLastId() { - return db.profileDao().getLastId(); - } - - - public Context getContext() - { - return mContext; - } - - public void checkDevModePassword() { - try { - devMode = Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", appConfig.devModePassword).equals("ok here you go it's enabled now") - || BuildConfig.DEBUG; - } catch (Exception e) { - e.printStackTrace(); - devMode = false; - } - } - -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt new file mode 100644 index 00000000..65836b5e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -0,0 +1,387 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon +import android.os.Build +import android.provider.Settings +import android.util.Log +import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDexApplication +import androidx.work.Configuration +import cat.ereza.customactivityoncrash.config.CaocConfig +import com.chuckerteam.chucker.api.ChuckerCollector +import com.chuckerteam.chucker.api.ChuckerInterceptor +import com.chuckerteam.chucker.api.RetentionManager +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.messaging.FirebaseMessaging +import com.google.gson.Gson +import com.hypertrack.hyperlog.HyperLog +import com.mikepenz.iconics.Iconics +import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont +import im.wangchao.mhttp.MHttp +import kotlinx.coroutines.* +import me.leolin.shortcutbadger.ShortcutBadger +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.network.NetworkUtils +import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar +import pl.szczodrzynski.edziennik.sync.SyncWorker +import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity +import pl.szczodrzynski.edziennik.utils.* +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.managers.* +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext + +class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { + companion object { + @Volatile + lateinit var db: AppDb + val config: Config by lazy { Config(db) } + var profile: Profile by mutableLazy { Profile(0, 0, 0, "") } + val profileId + get() = profile.id + + var devMode = false + var debugMode = false + } + + val notificationChannelsManager by lazy { NotificationChannelsManager(this) } + val userActionManager by lazy { UserActionManager(this) } + val gradesManager by lazy { GradesManager(this) } + val timetableManager by lazy { TimetableManager(this) } + val eventManager by lazy { EventManager(this) } + val permissionManager by lazy { PermissionManager(this) } + val attendanceManager by lazy { AttendanceManager(this) } + + val db + get() = App.db + val config + get() = App.config + val profile + get() = App.profile + val profileId + get() = App.profileId + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + override fun getWorkManagerConfiguration() = Configuration.Builder() + .setMinimumLoggingLevel(Log.VERBOSE) + .build() + + val permissionChecker by lazy { PermissionChecker(this) } + val networkUtils by lazy { NetworkUtils(this) } + val gson by lazy { Gson() } + + /* _ _ _______ _______ _____ + | | | |__ __|__ __| __ \ + | |__| | | | | | | |__) | + | __ | | | | | | ___/ + | | | | | | | | | | + |_| |_| |_| |_| |*/ + val http: OkHttpClient by lazy { + val builder = OkHttpClient.Builder() + .cache(null) + .followRedirects(true) + .followSslRedirects(true) + .retryOnConnectionFailure(true) + .cookieJar(cookieJar) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + builder.installHttpsSupport(this) + + if (debugMode || BuildConfig.DEBUG) { + 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) + } + + builder.build() + } + val httpLazy: OkHttpClient by lazy { + http.newBuilder() + .followRedirects(false) + .followSslRedirects(false) + .build() + } + val cookieJar by lazy { DumbCookieJar(this) } + + /* _____ _ _ + / ____(_) | | + | (___ _ __ _ _ __ __ _| |_ _ _ _ __ ___ + \___ \| |/ _` | '_ \ / _` | __| | | | '__/ _ \ + ____) | | (_| | | | | (_| | |_| |_| | | | __/ + |_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___| + __/ | + |__*/ + val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" } + private var unreadBadgesAvailable = true + + /* _____ _ + / ____| | | + ___ _ __ | | _ __ ___ __ _| |_ ___ + / _ \| '_ \| | | '__/ _ \/ _` | __/ _ \ + | (_) | | | | |____| | | __/ (_| | || __/ + \___/|_| |_|\_____|_| \___|\__,_|\__\__*/ + override fun onCreate() { + super.onCreate() + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) + CaocConfig.Builder.create() + .backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM) + .enabled(true) + .showErrorDetails(true) + .showRestartButton(true) + .logErrorOnRestart(true) + .trackActivities(true) + .minTimeBetweenCrashesMs(60*1000) + .errorDrawable(R.drawable.ic_rip) + .restartActivity(MainActivity::class.java) + .errorActivity(CrashActivity::class.java) + .apply() + Iconics.init(applicationContext) + Iconics.registerFont(SzkolnyFont) + App.db = AppDb(this) + Themes.themeInt = config.ui.theme + debugMode = config.debugMode + MHttp.instance().customOkHttpClient(http) + + if (!profileLoadById(config.lastProfileId)) { + db.profileDao().firstId?.let { profileLoadById(it) } + } + + config.ui.language?.let { + setLanguage(it) + } + + devMode = BuildConfig.DEBUG + if (BuildConfig.DEBUG) + debugMode = true + + Signing.getCert(this) + + launch { + withContext(Dispatchers.Default) { + config.migrate(this@App) + + if (config.devModePassword != null) + checkDevModePassword() + debugMode = devMode || config.debugMode + + if (config.sync.enabled) + SyncWorker.scheduleNext(this@App, false) + else + SyncWorker.cancelNext(this@App) + + if (config.sync.notifyAboutUpdates) + UpdateWorker.scheduleNext(this@App, false) + else + UpdateWorker.cancelNext(this@App) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + val shortcutManager = getSystemService(ShortcutManager::class.java) + + val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable") + .setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)) + .build() + + val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda") + .setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA)) + .build() + + val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades") + .setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES)) + .build() + + val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks") + .setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK)) + .build() + + val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages") + .setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES)) + .build() + + shortcutManager.dynamicShortcuts = listOf( + shortcutTimetable, + shortcutAgenda, + shortcutGrades, + shortcutHomework, + shortcutMessages + ) + } // shortcuts - end + + notificationChannelsManager.registerAllChannels() + + + if (config.appInstalledTime == 0L) + try { + config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime + config.appRateSnackbarTime = config.appInstalledTime + 7 * DAY * MS + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + + val pushMobidziennikApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setProjectId("mobidziennik") + .setStorageBucket("mobidziennik.appspot.com") + .setDatabaseUrl("https://mobidziennik.firebaseio.com") + .setGcmSenderId("747285019373") + .setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE") + .setApplicationId("1:747285019373:android:f6341bf7b158621d") + .build(), + "Mobidziennik2" + ) + + val pushLibrusApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setProjectId("synergiadru") + .setStorageBucket("synergiadru.appspot.com") + .setDatabaseUrl("https://synergiadru.firebaseio.com") + .setGcmSenderId("513056078587") + .setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o") + .setApplicationId("1:513056078587:android:1e29083b760af544") + .build(), + "Librus" + ) + + val pushVulcanApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setProjectId("dzienniczekplus") + .setStorageBucket("dzienniczekplus.appspot.com") + .setDatabaseUrl("https://dzienniczekplus.firebaseio.com") + .setGcmSenderId("987828170337") + .setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA") + .setApplicationId("1:987828170337:android:ac97431a0a4578c3") + .build(), + "Vulcan" + ) + + try { + FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + d("Firebase", "Got App token: $token") + config.sync.tokenApp = token + } + FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + d("Firebase", "Got Mobidziennik2 token: $token") + if (token != config.sync.tokenMobidziennik) { + config.sync.tokenMobidziennik = token + config.sync.tokenMobidziennikList = listOf() + } + } + FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + d("Firebase", "Got Librus token: $token") + if (token != config.sync.tokenLibrus) { + config.sync.tokenLibrus = token + config.sync.tokenLibrusList = listOf() + } + } + FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + d("Firebase", "Got Vulcan token: $token") + if (token != config.sync.tokenVulcan) { + config.sync.tokenVulcan = token + config.sync.tokenVulcanList = listOf() + } + } + FirebaseMessaging.getInstance().subscribeToTopic(packageName) + } catch (e: IllegalStateException) { + e.printStackTrace() + } + } + + db.metadataDao().countUnseen().observeForever { count: Int -> + if (unreadBadgesAvailable) + unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count) + } + } + } + + private fun profileLoadById(profileId: Int): Boolean { + db.profileDao().getByIdNow(profileId)?.also { + App.profile = it + App.config.lastProfileId = it.id + return true + } + return false + } + fun profileLoad(profileId: Int, onSuccess: (profile: Profile) -> Unit) { + launch { + val success = withContext(Dispatchers.Default) { + profileLoadById(profileId) + } + if (success) + onSuccess(profile) + else + profileLoadLast(onSuccess) + } + } + fun profileLoadLast(onSuccess: (profile: Profile) -> Unit) { + launch { + val success = withContext(Dispatchers.Default) { + profileLoadById(db.profileDao().lastId ?: return@withContext false) + } + if (!success) { + EventBus.getDefault().post(ProfileListEmptyEvent()) + } + else { + onSuccess(profile) + } + } + } + fun profileSave() = profileSave(profile) + fun profileSave(profile: Profile) { + launch(Dispatchers.Default) { + App.db.profileDao().add(profile) + } + } + + fun checkDevModePassword() { + devMode = try { + Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", config.devModePassword) == "ok here you go it's enabled now" || BuildConfig.DEBUG + } catch (e: Exception) { + e.printStackTrace() + false + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.java b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.java deleted file mode 100644 index d9c97a16..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-11. - */ - -package pl.szczodrzynski.edziennik; - -import android.graphics.Paint; -import android.widget.TextView; - -import androidx.databinding.BindingAdapter; - -public class Binding { - @BindingAdapter("strikeThrough") - public static void strikeThrough(TextView textView, Boolean strikeThrough) { - if (strikeThrough) { - textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt new file mode 100644 index 00000000..3dc9d0a6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-11. + */ +package pl.szczodrzynski.edziennik + +import android.graphics.Paint +import android.widget.TextView +import androidx.databinding.BindingAdapter + +object Binding { + @JvmStatic + @BindingAdapter("strikeThrough") + fun strikeThrough(textView: TextView, strikeThrough: Boolean) { + if (strikeThrough) { + textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG + } else { + textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index 1632a3bf..7306109a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -2,9 +2,17 @@ package pl.szczodrzynski.edziennik import android.Manifest import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.content.res.ColorStateList import android.content.res.Resources +import android.database.Cursor +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.Rect import android.graphics.Typeface import android.graphics.drawable.Drawable import android.os.Build @@ -13,29 +21,71 @@ import android.text.* import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan -import android.util.LongSparseArray -import android.util.SparseArray -import android.util.TypedValue +import android.util.* +import android.util.Base64 +import android.util.Base64.NO_WRAP +import android.util.Base64.encodeToString import android.view.View -import android.widget.TextView +import android.view.WindowManager +import android.widget.* import androidx.annotation.* import androidx.core.app.ActivityCompat +import androidx.core.database.getIntOrNull +import androidx.core.database.getLongOrNull +import androidx.core.database.getStringOrNull import androidx.core.util.forEach import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import androidx.viewpager.widget.ViewPager +import com.google.android.gms.security.ProviderInstaller +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject +import com.google.gson.JsonParser import im.wangchao.mhttp.Response -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team -import pl.szczodrzynski.navlib.R -import pl.szczodrzynski.navlib.getColorFromRes +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.TlsVersion +import okio.Buffer +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException +import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.network.TLSSocketFactory +import pl.szczodrzynski.edziennik.utils.models.Time +import java.io.InterruptedIOException +import java.io.PrintWriter +import java.io.StringWriter +import java.math.BigInteger +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.nio.charset.Charset +import java.security.KeyStore +import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.* import java.util.zip.CRC32 +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLException +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager +import kotlin.Pair fun List.byId(id: Long) = firstOrNull { it.id == id } @@ -51,23 +101,56 @@ fun JsonObject?.getString(key: String): String? = get(key)?.let { if (it.isJsonN fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) null else it.asInt } fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (it.isJsonNull) null else it.asLong } fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(it.isJsonNull) null else it.asFloat } -fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonNull) null else it.asJsonObject } -fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonNull) null else it.asJsonArray } +fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(it.isJsonNull) null else it.asCharacter } +fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonObject) it.asJsonObject else null } +fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonArray) it.asJsonArray else null } fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (it.isJsonNull) defaultValue else it.asBoolean } ?: defaultValue fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (it.isJsonNull) defaultValue else it.asString } ?: defaultValue fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (it.isJsonNull) defaultValue else it.asInt } ?: defaultValue fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (it.isJsonNull) defaultValue else it.asLong } ?: defaultValue fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(it.isJsonNull) defaultValue else it.asFloat } ?: defaultValue -fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonObject } ?: defaultValue -fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonArray } ?: defaultValue +fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(it.isJsonNull) defaultValue else it.asCharacter } ?: defaultValue +fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonObject) it.asJsonObject else defaultValue } ?: defaultValue +fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonArray) it.asJsonArray else defaultValue } ?: defaultValue -fun JsonArray?.asJsonObjectList() = this?.map { it.asJsonObject } +fun JsonArray.getBoolean(key: Int): Boolean? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asBoolean } +fun JsonArray.getString(key: Int): String? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asString } +fun JsonArray.getInt(key: Int): Int? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asInt } +fun JsonArray.getLong(key: Int): Long? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asLong } +fun JsonArray.getFloat(key: Int): Float? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asFloat } +fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asCharacter } +fun JsonArray.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null } +fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null } + +fun String.toJsonObject(): JsonObject? = try { JsonParser().parse(this).asJsonObject } catch (ignore: Exception) { null } + +operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value) +operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: Number) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: Char) = this.addProperty(key, value) + +operator fun Profile.set(key: String, value: JsonElement) = this.studentData.add(key, value) +operator fun Profile.set(key: String, value: Boolean) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: String?) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: Number) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: Char) = this.studentData.addProperty(key, value) + +fun JsonArray.asJsonObjectList() = this.mapNotNull { it.asJsonObject } fun CharSequence?.isNotNullNorEmpty(): Boolean { return this != null && this.isNotEmpty() } +fun Collection?.isNotNullNorEmpty(): Boolean { + return this != null && this.isNotEmpty() +} + +fun CharSequence?.isNotNullNorBlank(): Boolean { + return this != null && this.isNotBlank() +} + fun currentTimeUnix() = System.currentTimeMillis() / 1000 fun Bundle?.getInt(key: String, defaultValue: Int): Int { @@ -83,12 +166,38 @@ fun Bundle?.getString(key: String, defaultValue: String): String { return this?.getString(key, defaultValue) ?: defaultValue } -fun String.fixName(): String { - return this.fixWhiteSpaces().toProperCase() +fun Bundle?.getIntOrNull(key: String): Int? { + return this?.get(key) as? Int +} +fun Bundle?.get(key: String): T? { + return this?.get(key) as? T? } +/** + * ` The quick BROWN_fox Jumps OveR THE LAZy-DOG. ` + * + * converts to + * + * `The Quick Brown_fox Jumps Over The Lazy-Dog.` + */ +fun String?.fixName(): String { + return this?.fixWhiteSpaces()?.toProperCase() ?: "" +} + +/** + * `The quick BROWN_fox Jumps OveR THE LAZy-DOG.` + * + * converts to + * + * `The Quick Brown_fox Jumps Over The Lazy-Dog.` + */ fun String.toProperCase(): String = changeStringCase(this) +/** + * `John Smith` -> `Smith John` + * + * `JOHN SMith` -> `SMith JOHN` + */ fun String.swapFirstLastName(): String { return this.split(" ").let { if (it.size > 1) @@ -98,15 +207,13 @@ fun String.swapFirstLastName(): String { } } -fun String.getFirstLastName(): Pair? { +fun String.splitName(): Pair? { return this.split(" ").let { if (it.size >= 2) Pair(it[0], it[1]) else null } } -fun String.getLastFirstName() = this.getFirstLastName() - fun changeStringCase(s: String): String { val delimiters = " '-/" val sb = StringBuilder() @@ -136,40 +243,68 @@ fun String.getShortName(): String { } } +/** + * "John Smith" -> "JS" + * + * "JOHN SMith" -> "JS" + * + * "John" -> "J" + * + * "John " -> "J" + * + * "John Smith " -> "JS" + * + * " " -> "" + * + * " " -> "" + */ +fun String?.getNameInitials(): String { + if (this.isNullOrBlank()) return "" + return this.toUpperCase().fixWhiteSpaces().split(" ").take(2).map { it[0] }.joinToString("") +} + fun List.join(delimiter: String): String { - return this.joinToString(delimiter) + return concat(delimiter).toString() } -fun colorFromName(context: Context, name: String?): Int { - var crc = (name ?: "").crc16() - crc = (crc and 0xff) or (crc shr 8) - crc %= 16 - val color = when (crc) { - 13 -> R.color.md_red_500 - 4 -> R.color.md_pink_A400 - 2 -> R.color.md_purple_A400 - 9 -> R.color.md_deep_purple_A700 - 5 -> R.color.md_indigo_500 - 1 -> R.color.md_indigo_A700 - 6 -> R.color.md_cyan_A200 - 14 -> R.color.md_teal_400 - 15 -> R.color.md_green_500 - 7 -> R.color.md_yellow_A700 - 3 -> R.color.md_deep_orange_A400 - 8 -> R.color.md_deep_orange_A700 - 10 -> R.color.md_brown_500 - 12 -> R.color.md_grey_400 - 11 -> R.color.md_blue_grey_400 - else -> R.color.md_light_green_A700 - } - return context.getColorFromRes(color) +fun colorFromName(name: String?): Int { + val i = (name ?: "").crc32() + return when ((i / 10 % 16 + 1).toInt()) { + 13 -> 0xffF44336 + 4 -> 0xffF50057 + 2 -> 0xffD500F9 + 9 -> 0xff6200EA + 5 -> 0xffFFAB00 + 1 -> 0xff304FFE + 6 -> 0xff40C4FF + 14 -> 0xff26A69A + 15 -> 0xff00C853 + 7 -> 0xffFFD600 + 3 -> 0xffFF3D00 + 8 -> 0xffDD2C00 + 10 -> 0xff795548 + 12 -> 0xff2979FF + 11 -> 0xffFF6D00 + else -> 0xff64DD17 + }.toInt() } -fun MutableList.filterOutArchived(): MutableList { - this.removeAll { it.archived } - return this +fun colorFromCssName(name: String): Int { + return when (name) { + "red" -> 0xffff0000 + "green" -> 0xff008000 + "blue" -> 0xff0000ff + "violet" -> 0xffee82ee + "brown" -> 0xffa52a2a + "orange" -> 0xffffa500 + "black" -> 0xff000000 + "white" -> 0xffffffff + else -> -1 + }.toInt() } +fun List.filterOutArchived() = this.filter { !it.archived } + fun Activity.isStoragePermissionGranted(): Boolean { return if (Build.VERSION.SDK_INT >= 23) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { @@ -196,6 +331,7 @@ const val DAY = 24L*HOUR const val WEEK = 7L*DAY const val MONTH = 30L*DAY const val YEAR = 365L*DAY +const val MS = 1000L fun LongSparseArray.values(): List { val result = mutableListOf() @@ -205,6 +341,13 @@ fun LongSparseArray.values(): List { return result } +fun SparseArray<*>.keys(): List { + val result = mutableListOf() + forEach { key, _ -> + result += key + } + return result +} fun SparseArray.values(): List { val result = mutableListOf() forEach { _, value -> @@ -213,6 +356,36 @@ fun SparseArray.values(): List { return result } +fun SparseIntArray.keys(): List { + val result = mutableListOf() + forEach { key, _ -> + result += key + } + return result +} +fun SparseIntArray.values(): List { + val result = mutableListOf() + forEach { _, value -> + result += value + } + return result +} + +fun List>.keys(): List { + val result = mutableListOf() + forEach { pair -> + result += pair.first + } + return result +} +fun List>.values(): List { + val result = mutableListOf() + forEach { pair -> + result += pair.second + } + return result +} + fun List.toSparseArray(destination: SparseArray, key: (T) -> Int) { forEach { destination.put(key(it), it) @@ -282,7 +455,7 @@ operator fun MatchResult.get(group: Int): String { return groupValues[group] } -fun Activity.setLanguage(language: String) { +fun Context.setLanguage(language: String) { val locale = Locale(language.toLowerCase(Locale.ROOT)) val configuration = resources.configuration Locale.setDefault(locale) @@ -291,7 +464,6 @@ fun Activity.setLanguage(language: String) { } configuration.locale = locale resources.updateConfiguration(configuration, resources.displayMetrics) - baseContext.resources.updateConfiguration(configuration, baseContext.resources.displayMetrics) } /* @@ -350,6 +522,34 @@ fun String.crc32(): Long { return crc.value } +fun String.hmacSHA1(password: String): String { + val key = SecretKeySpec(password.toByteArray(), "HmacSHA1") + + val mac = Mac.getInstance("HmacSHA1").apply { + init(key) + update(this@hmacSHA1.toByteArray()) + } + + return encodeToString(mac.doFinal(), NO_WRAP) +} + +fun String.md5(): String { + val md = MessageDigest.getInstance("MD5") + return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0') +} + +fun String.sha256(): ByteArray { + val md = MessageDigest.getInstance("SHA-256") + md.update(toByteArray()) + return md.digest() +} + +fun RequestBody.bodyToString(): String { + val buffer = Buffer() + writeTo(buffer) + return buffer.readUtf8() +} + fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this) fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable { @@ -367,6 +567,56 @@ fun CharSequence?.asItalicSpannable(): Spannable { spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) return spannable } +fun CharSequence?.asBoldSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} +fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable { + val spannable = SpannableString(this) + if (substring == null) { + spans.forEach { + spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + else if (substring.isNotEmpty()) { + val string = + if (ignoreDiacritics) + this.cleanDiacritics() + else this + + var index = string.indexOf(substring, ignoreCase = ignoreCase) + .takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase) + while (index >= 0) { + spans.forEach { + spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) + .takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) + } + } + return spannable +} + +fun CharSequence.cleanDiacritics(): String { + val nameClean = StringBuilder() + forEach { + val ch = when (it) { + 'ż' -> 'z' + 'ó' -> 'o' + 'ł' -> 'l' + 'ć' -> 'c' + 'ę' -> 'e' + 'ś' -> 's' + 'ą' -> 'a' + 'ź' -> 'z' + 'ń' -> 'n' + else -> it + } + nameClean.append(ch) + } + return nameClean.toString() +} /** * Returns a new read-only list only of those given elements, that are not empty. @@ -374,20 +624,22 @@ fun CharSequence?.asItalicSpannable(): Spannable { */ fun listOfNotEmpty(vararg elements: T): List = elements.filterNot { it.isEmpty() } -fun List.concat(delimiter: String? = null): CharSequence { +fun List.concat(delimiter: CharSequence? = null): CharSequence { if (this.isEmpty()) { return "" } if (this.size == 1) { - return this[0] + return this[0] ?: "" } - var spanned = false - for (piece in this) { - if (piece is Spanned) { - spanned = true - break + var spanned = delimiter is Spanned + if (!spanned) { + for (piece in this) { + if (piece is Spanned) { + spanned = true + break + } } } @@ -395,6 +647,8 @@ fun List.concat(delimiter: String? = null): CharSequence { if (spanned) { val ssb = SpannableStringBuilder() for (piece in this) { + if (piece == null) + continue if (!first && delimiter != null) ssb.append(delimiter) first = false @@ -404,6 +658,8 @@ fun List.concat(delimiter: String? = null): CharSequence { } else { val sb = StringBuilder() for (piece in this) { + if (piece == null) + continue if (!first && delimiter != null) sb.append(delimiter) first = false @@ -417,22 +673,96 @@ fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) { text = context.getString(resid, *formatArgs) } -fun JsonObject(vararg properties: Pair): JsonObject { +fun MaterialAlertDialogBuilder.setTitle(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { + setTitle(context.getString(resid, *formatArgs)) + return this +} + +fun MaterialAlertDialogBuilder.setMessage(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { + setMessage(context.getString(resid, *formatArgs)) + return this +} + +fun JsonObject(vararg properties: Pair): JsonObject { return JsonObject().apply { for (property in properties) { when (property.second) { - is JsonElement -> add(property.first, property.second as JsonElement) - is String -> addProperty(property.first, property.second as String) - is Char -> addProperty(property.first, property.second as Char) - is Number -> addProperty(property.first, property.second as Number) - is Boolean -> addProperty(property.first, property.second as Boolean) + is JsonElement -> add(property.first, property.second as JsonElement?) + is String -> addProperty(property.first, property.second as String?) + is Char -> addProperty(property.first, property.second as Char?) + is Number -> addProperty(property.first, property.second as Number?) + is Boolean -> addProperty(property.first, property.second as Boolean?) } } } } +fun JsonArray(vararg properties: Any?): JsonArray { + return JsonArray().apply { + for (property in properties) { + when (property) { + is JsonElement -> add(property as JsonElement?) + is String -> add(property as String?) + is Char -> add(property as Char?) + is Number -> add(property as Number?) + is Boolean -> add(property as Boolean?) + } + } + } +} + +fun Bundle(vararg properties: Pair): Bundle { + return Bundle().apply { + for (property in properties) { + when (property.second) { + is String -> putString(property.first, property.second as String?) + is Char -> putChar(property.first, property.second as Char) + is Int -> putInt(property.first, property.second as Int) + is Long -> putLong(property.first, property.second as Long) + is Float -> putFloat(property.first, property.second as Float) + is Short -> putShort(property.first, property.second as Short) + is Double -> putDouble(property.first, property.second as Double) + is Boolean -> putBoolean(property.first, property.second as Boolean) + } + } + } +} +fun Intent(action: String? = null, vararg properties: Pair): Intent { + return Intent(action).putExtras(Bundle(*properties)) +} +fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair): Intent { + return Intent(packageContext, cls).putExtras(Bundle(*properties)) +} + +fun Bundle.toJsonObject(): JsonObject { + val json = JsonObject() + keySet()?.forEach { key -> + get(key)?.let { + when (it) { + is String -> json.addProperty(key, it) + is Char -> json.addProperty(key, it) + is Int -> json.addProperty(key, it) + is Long -> json.addProperty(key, it) + is Float -> json.addProperty(key, it) + is Short -> json.addProperty(key, it) + is Double -> json.addProperty(key, it) + is Boolean -> json.addProperty(key, it) + is Bundle -> json.add(key, it.toJsonObject()) + else -> json.addProperty(key, it.toString()) + } + } + } + return json +} +fun Intent.toJsonObject() = extras?.toJsonObject() + fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0 fun JsonArray.isEmpty(): Boolean = this.size() == 0 +operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o) +operator fun JsonArray.plusAssign(o: String) = this.add(o) +operator fun JsonArray.plusAssign(o: Char) = this.add(o) +operator fun JsonArray.plusAssign(o: Number) = this.add(o) +operator fun JsonArray.plusAssign(o: Boolean) = this.add(o) @Suppress("UNCHECKED_CAST") inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) { @@ -441,6 +771,33 @@ inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) { } } +@Suppress("UNCHECKED_CAST") +inline fun T.onLongClick(crossinline onLongClickListener: (v: T) -> Boolean) { + setOnLongClickListener { v: View -> + onLongClickListener(v as T) + } +} + +@Suppress("UNCHECKED_CAST") +inline fun T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) { + setOnCheckedChangeListener { buttonView, isChecked -> + onChangeListener(buttonView as T, isChecked) + } +} + +@Suppress("UNCHECKED_CAST") +inline fun T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) { + clearOnCheckedChangeListeners() + addOnCheckedChangeListener { buttonView, isChecked -> + onChangeListener(buttonView as T, isChecked) + } +} + +fun View.attachToastHint(stringRes: Int) = onLongClick { + Toast.makeText(it.context, stringRes, Toast.LENGTH_SHORT).show() + true +} + fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) { observe(lifecycleOwner, object : Observer { override fun onChanged(t: T?) { @@ -494,4 +851,410 @@ fun View.findParentById(targetId: Int): View? { return viewParent.findParentById(targetId) } return null -} \ No newline at end of file +} + +fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: suspend CoroutineScope.() -> Unit) = launch { + delay(delayMillis) + if (repeatMillis > 0) { + while (true) { + action() + delay(repeatMillis) + } + } else { + action() + } +} + +operator fun Time?.compareTo(other: Time?): Int { + if (this == null && other == null) + return 0 + if (this == null) + return -1 + if (other == null) + return 1 + return this.compareTo(other) +} + +operator fun StringBuilder.plusAssign(str: String?) { + this.append(str) +} + +fun Context.timeTill(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String { + val parts = mutableListOf>() + + val hours = time / 3600 + val minutes = (time - hours*3600) / 60 + val seconds = time - minutes*60 - hours*3600 + + if (!countInSeconds) { + var prefixAdded = false + if (hours > 0) { + if (!prefixAdded) parts += R.plurals.time_till_text to hours + prefixAdded = true + parts += R.plurals.time_till_hours to hours + } + if (minutes > 0) { + if (!prefixAdded) parts += R.plurals.time_till_text to minutes + prefixAdded = true + parts += R.plurals.time_till_minutes to minutes + } + if (hours == 0 && minutes < 10) { + if (!prefixAdded) parts += R.plurals.time_till_text to seconds + prefixAdded = true + parts += R.plurals.time_till_seconds to seconds + } + } else { + parts += R.plurals.time_till_text to time + parts += R.plurals.time_till_seconds to time + } + + return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) } +} + +fun Context.timeLeft(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String { + val parts = mutableListOf>() + + val hours = time / 3600 + val minutes = (time - hours*3600) / 60 + val seconds = time - minutes*60 - hours*3600 + + if (!countInSeconds) { + var prefixAdded = false + if (hours > 0) { + if (!prefixAdded) parts += R.plurals.time_left_text to hours + prefixAdded = true + parts += R.plurals.time_left_hours to hours + } + if (minutes > 0) { + if (!prefixAdded) parts += R.plurals.time_left_text to minutes + prefixAdded = true + parts += R.plurals.time_left_minutes to minutes + } + if (hours == 0 && minutes < 10) { + if (!prefixAdded) parts += R.plurals.time_left_text to seconds + prefixAdded = true + parts += R.plurals.time_left_seconds to seconds + } + } else { + parts += R.plurals.time_left_text to time + parts += R.plurals.time_left_seconds to time + } + + return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) } +} + +inline fun Any?.instanceOfOrNull(): T? { + return when (this) { + is T -> this + else -> null + } +} + +fun Drawable.setTintColor(color: Int): Drawable { + colorFilter = PorterDuffColorFilter( + color, + PorterDuff.Mode.SRC_ATOP + ) + return this +} + +inline fun List.ifNotEmpty(block: (List) -> Unit) { + if (!isEmpty()) + block(this) +} + +val String.firstLettersName: String + get() { + var nameShort = "" + this.split(" ").forEach { + if (it.isBlank()) + return@forEach + nameShort += it[0].toLowerCase() + } + return nameShort + } + +val Throwable.stackTraceString: String + get() { + val sw = StringWriter() + printStackTrace(PrintWriter(sw)) + return sw.toString() + } + +inline fun LongSparseArray.filter(predicate: (T) -> Boolean): List { + val destination = ArrayList() + this.forEach { _, element -> if (predicate(element)) destination.add(element) } + return destination +} + +fun CharSequence.replace(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence = + splitToSequence(oldValue, ignoreCase = ignoreCase).toList().concat(newValue) + +fun Int.toColorStateList(): ColorStateList { + val states = arrayOf( + intArrayOf( android.R.attr.state_enabled ), + intArrayOf(-android.R.attr.state_enabled ), + intArrayOf(-android.R.attr.state_checked ), + intArrayOf( android.R.attr.state_pressed ) + ) + + val colors = intArrayOf( + this, + this, + this, + this + ) + + return ColorStateList(states, colors) +} + +fun SpannableStringBuilder.appendText(text: CharSequence): SpannableStringBuilder { + append(text) + return this +} +fun SpannableStringBuilder.appendSpan(text: CharSequence, what: Any, flags: Int): SpannableStringBuilder { + val start: Int = length + append(text) + setSpan(what, start, length, flags) + return this +} + +fun joinNotNullStrings(delimiter: String = "", vararg parts: String?): String { + var first = true + val sb = StringBuilder() + for (part in parts) { + if (part == null) + continue + if (!first) + sb += delimiter + first = false + sb += part + } + return sb.toString() +} + +fun String.notEmptyOrNull(): String? { + return if (isEmpty()) + null + else + this +} + +fun String.base64Encode(): String { + return encodeToString(toByteArray(), NO_WRAP) +} +fun ByteArray.base64Encode(): String { + return encodeToString(this, NO_WRAP) +} +fun String.base64Decode(): ByteArray { + return Base64.decode(this, Base64.DEFAULT) +} +fun String.base64DecodeToString(): String { + return Base64.decode(this, Base64.DEFAULT).toString(Charset.defaultCharset()) +} + +fun CheckBox.trigger() { isChecked = !isChecked } + +fun Context.plural(@PluralsRes resId: Int, value: Int): String = resources.getQuantityString(resId, value, value) + +fun Context.getNotificationTitle(type: Int): String { + return getString(when (type) { + Notification.TYPE_UPDATE -> R.string.notification_type_update + Notification.TYPE_ERROR -> R.string.notification_type_error + Notification.TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change + Notification.TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change + Notification.TYPE_NEW_GRADE -> R.string.notification_type_new_grade + Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event + Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework + Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event + Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework + Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event + Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message + Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice + Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance + Notification.TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message + Notification.TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number + Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message + Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement + Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving + Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence + Notification.TYPE_GENERAL -> R.string.notification_type_general + else -> R.string.notification_type_general + }) +} + +fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName)) +fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName)) +fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName)) + +fun OkHttpClient.Builder.installHttpsSupport(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { + try { + try { + ProviderInstaller.installIfNeeded(context) + } catch (e: Exception) { + Log.e("OkHttpTLSCompat", "Play Services not found or outdated") + + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(null as KeyStore?) + + val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager? + ?: return + + val sc = SSLContext.getInstance("TLSv1.2") + sc.init(null, null, null) + sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager) + val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_1) + .tlsVersions(TlsVersion.TLS_1_2) + .build() + val specs: MutableList = ArrayList() + specs.add(cs) + specs.add(ConnectionSpec.COMPATIBLE_TLS) + specs.add(ConnectionSpec.CLEARTEXT) + connectionSpecs(specs) + } + } catch (exc: Exception) { + Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc) + } + } +} + +fun CharSequence.containsAll(list: List, ignoreCase: Boolean = false): Boolean { + for (i in list) { + if (!contains(i, ignoreCase)) + return false + } + return true +} + +inline fun RadioButton.setOnSelectedListener(crossinline listener: (buttonView: CompoundButton) -> Unit) + = setOnCheckedChangeListener { buttonView, isChecked -> if (isChecked) listener(buttonView) } + +fun Response.toErrorCode() = when (this.code()) { + 400 -> ERROR_REQUEST_HTTP_400 + 401 -> ERROR_REQUEST_HTTP_401 + 403 -> ERROR_REQUEST_HTTP_403 + 404 -> ERROR_REQUEST_HTTP_404 + 405 -> ERROR_REQUEST_HTTP_405 + 410 -> ERROR_REQUEST_HTTP_410 + 424 -> ERROR_REQUEST_HTTP_424 + 500 -> ERROR_REQUEST_HTTP_500 + 503 -> ERROR_REQUEST_HTTP_503 + else -> null +} +fun Throwable.toErrorCode() = when (this) { + is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND + is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR + is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT + is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET + is SzkolnyApiException -> this.error?.toErrorCode() + else -> null +} +private fun ApiResponse.Error.toErrorCode() = when (this.code) { + else -> ERROR_API_EXCEPTION +} +fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this) + +inline fun ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? { + if (a != null && b != null) { + return code(a, b) + } + return null +} + +@kotlin.jvm.JvmName("averageOrNullOfInt") +fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it } +@kotlin.jvm.JvmName("averageOrNullOfFloat") +fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it } + +fun String.copyToClipboard(context: Context) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText("Tekst", this) + clipboard.primaryClip = clipData +} + +fun TextView.getTextPosition(range: IntRange): Rect { + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + + // Initialize global value + var parentTextViewRect = Rect() + + // Initialize values for the computing of clickedText position + //val completeText = parentTextView.text as SpannableString + val textViewLayout = this.layout + + val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText) + val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText) + var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText) + var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText) + + // Get the rectangle of the clicked text + val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText) + val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText) + val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset + textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect) + + // Update the rectangle position to his real position on screen + val parentTextViewLocation = intArrayOf(0, 0) + this.getLocationOnScreen(parentTextViewLocation) + + val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop) + parentTextViewRect.top += parentTextViewTopAndBottomOffset + parentTextViewRect.bottom += parentTextViewTopAndBottomOffset + + // In the case of multi line text, we have to choose what rectangle take + if (keywordIsInMultiLine) { + val screenHeight = windowManager.defaultDisplay.height + val dyTop = parentTextViewRect.top + val dyBottom = screenHeight - parentTextViewRect.bottom + val onTop = dyTop > dyBottom + + if (onTop) { + endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset); + } else { + parentTextViewRect = Rect() + textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect); + parentTextViewRect.top += parentTextViewTopAndBottomOffset; + parentTextViewRect.bottom += parentTextViewTopAndBottomOffset; + startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset); + } + } + + parentTextViewRect.left += ( + parentTextViewLocation[0] + + startXCoordinatesOfClickedText + + this.compoundPaddingLeft - + this.scrollX + ).toInt() + parentTextViewRect.right = ( + parentTextViewRect.left + + endXCoordinatesOfClickedText - + startXCoordinatesOfClickedText + ).toInt() + + return parentTextViewRect +} + +inline fun ViewPager.addOnPageSelectedListener(crossinline block: (position: Int) -> Unit) = addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) {} + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + override fun onPageSelected(position: Int) { block(position) } +}) + +val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener + get() = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (recyclerView.canScrollVertically(-1)) + this@onScrollListener.isEnabled = false + if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) + this@onScrollListener.isEnabled = true + } + } + +operator fun Iterable>.get(key: K): V? { + return firstOrNull { it.first == key }?.second +} + +fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 7e5eb17e..e1fe3b0c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -1,6 +1,5 @@ package pl.szczodrzynski.edziennik -import android.app.Activity import android.app.ActivityManager import android.content.BroadcastReceiver import android.content.Context @@ -9,7 +8,9 @@ import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable -import android.os.* +import android.os.Build +import android.os.Bundle +import android.os.Environment import android.provider.Settings import android.view.Gravity import android.view.View @@ -17,6 +18,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.navigation.NavOptions import com.danimahardhika.cafebar.CafeBar @@ -29,48 +31,62 @@ import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.ProfileDrawerItem import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem -import com.mikepenz.materialdrawer.model.interfaces.IProfile +import com.mikepenz.materialdrawer.model.interfaces.* +import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.droidsonroids.gif.GifDrawable -import pl.szczodrzynski.edziennik.App.APP_URL -import pl.szczodrzynski.edziennik.api.v2.events.* -import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.* +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Metadata.* +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding -import pl.szczodrzynski.edziennik.network.ServerRequest import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.SyncWorker +import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog +import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog +import pl.szczodrzynski.edziennik.ui.dialogs.UpdateAvailableDialog import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment -import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment +import pl.szczodrzynski.edziennik.ui.modules.base.MainSnackbar import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment +import pl.szczodrzynski.edziennik.ui.modules.debug.DebugFragment +import pl.szczodrzynski.edziennik.ui.modules.debug.LabFragment import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment -import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListFragment import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment -import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment +import pl.szczodrzynski.edziennik.ui.modules.messages.compose.MessagesComposeFragment +import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsListFragment import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment -import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.appManagerIntentList +import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT @@ -79,13 +95,13 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.drawer.NavDrawer import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem -import pl.szczodrzynski.navlib.drawer.items.withAppTitle import java.io.File import java.io.IOException import java.util.* +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), CoroutineScope { companion object { var useOldMessages = false @@ -98,6 +114,7 @@ class MainActivity : AppCompatActivity() { const val DRAWER_PROFILE_SYNC_ALL = 201 const val DRAWER_PROFILE_EXPORT_DATA = 202 const val DRAWER_PROFILE_MANAGE = 203 + const val DRAWER_PROFILE_MARK_ALL_AS_READ = 204 const val DRAWER_ITEM_HOME = 1 const val DRAWER_ITEM_TIMETABLE = 11 const val DRAWER_ITEM_AGENDA = 12 @@ -115,6 +132,9 @@ class MainActivity : AppCompatActivity() { const val TARGET_HELP = 502 const val TARGET_FEEDBACK = 120 const val TARGET_MESSAGES_DETAILS = 503 + const val TARGET_MESSAGES_COMPOSE = 504 + const val TARGET_WEB_PUSH = 140 + const val TARGET_LAB = 1000 const val HOME_ID = DRAWER_ITEM_HOME @@ -124,7 +144,7 @@ class MainActivity : AppCompatActivity() { // home item list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class) .withTitle(R.string.app_name) - .withIcon(CommunityMaterial.Icon2.cmd_home) + .withIcon(CommunityMaterial.Icon2.cmd_home_outline) .isInDrawer(true) .isStatic(true) .withPopToHome(false) @@ -135,50 +155,50 @@ class MainActivity : AppCompatActivity() { .isInDrawer(true) list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar) + .withIcon(CommunityMaterial.Icon.cmd_calendar_outline) .withBadgeTypeId(TYPE_EVENT) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box) + list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesListFragment::class) + .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline) .withBadgeTypeId(TYPE_GRADE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_email) + .withIcon(CommunityMaterial.Icon.cmd_email_outline) .withBadgeTypeId(TYPE_MESSAGE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class) - .withIcon(SzkolnyFont.Icon.szf_file_document_edit) + .withIcon(SzkolnyFont.Icon.szf_notebook_outline) .withBadgeTypeId(TYPE_HOMEWORK) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_message_alert) + .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline) .withBadgeTypeId(TYPE_NOTICE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar_remove) + .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline) .withBadgeTypeId(TYPE_ATTENDANCE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bulletin_board) + .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline) .withBadgeTypeId(TYPE_ANNOUNCEMENT) .isInDrawer(true) // static drawer items - list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bell_ring) + list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsListFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_settings) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) @@ -196,8 +216,12 @@ class MainActivity : AppCompatActivity() { .withDescription(R.string.drawer_manage_profiles_desc) .isInProfileList(false) + list += NavTarget(DRAWER_PROFILE_MARK_ALL_AS_READ, R.string.menu_mark_everything_as_read, null) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .isInProfileList(true) + list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null) - .withIcon(CommunityMaterial.Icon2.cmd_sync) + .withIcon(CommunityMaterial.Icon.cmd_download_outline) .isInProfileList(true) @@ -205,17 +229,31 @@ class MainActivity : AppCompatActivity() { list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class) list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class) list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class) - list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class) - list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) + list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class).withPopTo(DRAWER_ITEM_MESSAGES) + list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class) + list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class) + if (App.debugMode) { + list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) + list += NavTarget(TARGET_LAB, R.string.menu_lab, LabFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_flask_outline) + .isInDrawer(true) + .isBelowSeparator(true) + .isStatic(true) + } list } } + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + val b: ActivitySzkolnyBinding by lazy { ActivitySzkolnyBinding.inflate(layoutInflater) } val navView: NavView by lazy { b.navView } val drawer: NavDrawer by lazy { navView.drawer } val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet } + val mainSnackbar: MainSnackbar by lazy { MainSnackbar(this) } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } @@ -226,10 +264,11 @@ class MainActivity : AppCompatActivity() { private val fragmentManager by lazy { supportFragmentManager } private lateinit var navTarget: NavTarget - private val navTargetId + private var navArguments: Bundle? = null + val navTargetId get() = navTarget.id - private val navBackStack = mutableListOf() + private val navBackStack = mutableListOf>() private var navLoading = true /* ____ _____ _ @@ -241,16 +280,42 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + d(TAG, "Activity created") + setTheme(Themes.appTheme) - app.appConfig.language?.let { + app.config.ui.language?.let { setLanguage(it) } + if (App.profileId == 0) { + onProfileListEmptyEvent(ProfileListEmptyEvent()) + return + } + + d(TAG, "Profile is valid, inflating views") + setContentView(b.root) + mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) + when { + BuildConfig.VERSION_NAME.contains("nightly") -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + BuildConfig.VERSION_NAME.contains("daily") -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Daily\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + BuildConfig.DEBUG -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Debug\n"+BuildConfig.VERSION_NAME + } + else -> b.nightlyText.isVisible = false + } + navLoading = true b.navView.apply { @@ -303,15 +368,17 @@ class MainActivity : AppCompatActivity() { removeAllItems() toggleGroupEnabled = false textInputEnabled = false + onCloseListener = { + if (!app.config.ui.bottomSheetOpened) + app.config.ui.bottomSheetOpened = true + } } drawer.apply { - setAccountHeaderBackground(app.appConfig.headerBackground) + setAccountHeaderBackground(app.config.ui.headerBackground) drawerProfileListEmptyListener = { - app.appConfig.loginFinished = false - app.saveConfig("loginFinished") - profileListEmptyListener() + onProfileListEmptyEvent(ProfileListEmptyEvent()) } drawerItemSelectedListener = { id, position, drawerItem -> loadTarget(id) @@ -322,7 +389,7 @@ class MainActivity : AppCompatActivity() { false } drawerProfileLongClickListener = { _, profile, _, view -> - if (profile is ProfileDrawerItem) { + if (view != null && profile is ProfileDrawerItem) { showProfileContextMenu(profile, view) true } @@ -334,38 +401,38 @@ class MainActivity : AppCompatActivity() { drawerProfileSettingClickListener = this@MainActivity.profileSettingClickListener miniDrawerVisibleLandscape = null - miniDrawerVisiblePortrait = app.appConfig.miniDrawerVisible + miniDrawerVisiblePortrait = app.config.ui.miniMenuVisible } } navTarget = navTargetList[0] - var profileListEmpty = drawer.profileListEmpty - if (savedInstanceState != null) { intent?.putExtras(savedInstanceState) savedInstanceState.clear() } - if (!profileListEmpty) { - handleIntent(intent?.extras) - } - app.db.profileDao().allFull.observe(this, Observer { profiles -> - // TODO fix weird -1 profiles ??? - profiles.removeAll { it.id < 0 } - drawer.setProfileList(profiles) - if (profileListEmpty) { - profileListEmpty = false - handleIntent(intent?.extras) - } - else if (app.profile != null) { - drawer.currentProfile = app.profile.id + app.db.profileDao().all.observe(this, Observer { profiles -> + val allArchived = profiles.all { it.archived } + drawer.setProfileList(profiles.filter { it.id >= 0 && (!it.archived || allArchived) }.toMutableList()) + //prepend the archived profile if loaded + if (app.profile.archived && !allArchived) { + drawer.prependProfile(Profile( + id = app.profile.id, + loginStoreId = app.profile.loginStoreId, + loginStoreType = app.profile.loginStoreType, + name = app.profile.name, + subname = "Archiwum - ${app.profile.subname}" + ).also { + it.archived = true + }) } + drawer.currentProfile = App.profileId }) - // if null, getAllFull will load a profile and update drawerItems - if (app.profile != null) - setDrawerItems() + setDrawerItems() + + handleIntent(intent?.extras) app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters -> unreadCounters.map { @@ -375,66 +442,82 @@ class MainActivity : AppCompatActivity() { }) b.swipeRefreshLayout.isEnabled = true - b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } + b.swipeRefreshLayout.setOnRefreshListener { launch { syncCurrentFeature() } } b.swipeRefreshLayout.setColorSchemeResources( R.color.md_blue_500, R.color.md_amber_500, R.color.md_green_500 ) - isStoragePermissionGranted() - SyncWorker.scheduleNext(app) + UpdateWorker.scheduleNext(app) + + // if loaded profile is archived, switch to the up-to-date version of it + if (app.profile.archived) { + launch { + if (app.profile.archiveId != null) { + val profile = withContext(Dispatchers.IO) { + app.db.profileDao().getNotArchivedOf(app.profile.archiveId!!) + } + if (profile != null) + loadProfile(profile) + else + loadProfile(0) + } else { + loadProfile(0) + } + } + } // APP BACKGROUND - if (app.appConfig.appBackground != null) { + if (app.config.ui.appBackground != null) { try { - var bg = app.appConfig.appBackground - val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg") - if (bgDir.exists()) { - val files = bgDir.listFiles() - val r = Random() - val i = r.nextInt(files.size) - bg = files[i].toString() - } - val linearLayout = b.root - if (bg.endsWith(".gif")) { - linearLayout.background = GifDrawable(bg) - } else { - linearLayout.background = BitmapDrawable.createFromPath(bg) + app.config.ui.appBackground?.let { + var bg = it + val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg") + if (bgDir.exists()) { + val files = bgDir.listFiles() + val r = Random() + val i = r.nextInt(files.size) + bg = files[i].toString() + } + val linearLayout = b.root + if (bg.endsWith(".gif")) { + linearLayout.background = GifDrawable(bg) + } else { + linearLayout.background = BitmapDrawable.createFromPath(bg) + } } } catch (e: IOException) { e.printStackTrace() } } + // IT'S WINTER MY DUDES + val today = Date.getToday() + if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) { + b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) + } + // WHAT'S NEW DIALOG - if (app.appConfig.lastAppVersion != BuildConfig.VERSION_CODE) { - ServerRequest(app, app.requestScheme + APP_URL + "main.php?just_updated", "MainActivity/JU") - .run { e, result -> - Handler(Looper.getMainLooper()).post { - try { - ChangelogDialog().show(supportFragmentManager, "whats_new") - } catch (e2: Exception) { - e2.printStackTrace() - } - } - } - if (app.appConfig.lastAppVersion < 170) { + if (app.config.appVersion < BuildConfig.VERSION_CODE) { + // force an AppSync after update + app.config.sync.lastAppSync = 0L + ChangelogDialog(this) + if (app.config.appVersion < 170) { //Intent intent = new Intent(this, ChangelogIntroActivity.class); //startActivity(intent); } else { - app.appConfig.lastAppVersion = BuildConfig.VERSION_CODE - app.saveConfig("lastAppVersion") + app.config.appVersion = BuildConfig.VERSION_CODE } } // RATE SNACKBAR - if (app.appConfig.appRateSnackbarTime != 0L && app.appConfig.appRateSnackbarTime <= System.currentTimeMillis()) { + if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) { navView.coordinator.postDelayed({ CafeBar.builder(this) .content(R.string.rate_snackbar_text) - .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) + .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) .positiveText(R.string.rate_snackbar_positive) .positiveColor(-0xb350b0) .negativeText(R.string.rate_snackbar_negative) @@ -444,20 +527,17 @@ class MainActivity : AppCompatActivity() { .onPositive { cafeBar -> Utils.openGooglePlay(this) cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = 0 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = 0 } .onNegative { cafeBar -> Toast.makeText(this, "Szkoda, opinie innych pomagają mi rozwijać aplikację.", Toast.LENGTH_LONG).show() cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = 0 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = 0 } .onNeutral { cafeBar -> Toast.makeText(this, "OK", Toast.LENGTH_LONG).show() cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 } .autoDismiss(false) .swipeToDismiss(true) @@ -471,7 +551,7 @@ class MainActivity : AppCompatActivity() { bottomSheet.appendItems( BottomSheetPrimaryItem(false) .withTitle(R.string.menu_sync) - .withIcon(CommunityMaterial.Icon2.cmd_sync) + .withIcon(CommunityMaterial.Icon.cmd_download_outline) .withOnClickListener(View.OnClickListener { bottomSheet.close() SyncViewListDialog(this, navTargetId) @@ -479,42 +559,40 @@ class MainActivity : AppCompatActivity() { BottomSheetSeparatorItem(false), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_settings) - .withIcon(CommunityMaterial.Icon2.cmd_settings) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_feedback) - .withIcon(CommunityMaterial.Icon2.cmd_help_circle) + .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline) .withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) }) ) - if (App.devMode) { + if (App.debugMode) { bottomSheet += BottomSheetPrimaryItem(false) .withTitle(R.string.menu_debug) - .withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge) + .withIcon(CommunityMaterial.Icon.cmd_android_studio) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) }) } - - EventBus.getDefault().register(this) } - override fun onDestroy() { - EventBus.getDefault().unregister(this) - super.onDestroy() - } - - var profileListEmptyListener = { - startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY) - } private var profileSettingClickListener = { id: Int, view: View? -> when (id) { DRAWER_PROFILE_ADD_NEW -> { - LoginActivity.privacyPolicyAccepted = true - // else it would try to navigateUp onBackPressed, which it can't do. There is no parent fragment - LoginActivity.firstCompleted = false - profileListEmptyListener() + startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY) } DRAWER_PROFILE_SYNC_ALL -> { EdziennikTask.sync().enqueue(this) } + DRAWER_PROFILE_MARK_ALL_AS_READ -> { launch { + withContext(Dispatchers.Default) { + app.db.profileDao().allNow.forEach { profile -> + if (profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS) + app.db.metadataDao().setAllSeenExceptMessagesAndAnnouncements(profile.id, true) + else + app.db.metadataDao().setAllSeenExceptMessages(profile.id, true) + } + } + Toast.makeText(this@MainActivity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }} else -> { loadTarget(id) } @@ -530,20 +608,98 @@ class MainActivity : AppCompatActivity() { |_____/ \__, |_| |_|\___| __/ | |__*/ - fun syncCurrentFeature() { + suspend fun syncCurrentFeature() { + if (app.profile.archived) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_archived_title) + .setMessage( + R.string.profile_archived_text, + app.profile.studentSchoolYearStart, + app.profile.studentSchoolYearStart + 1 + ) + .setPositiveButton(R.string.ok, null) + .show() + swipeRefreshLayout.isRefreshing = false + return + } + if (app.profile.shouldArchive()) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_archiving_title) + .setMessage( + R.string.profile_archiving_format, + app.profile.dateYearEnd.formattedString + ) + .setPositiveButton(R.string.ok, null) + .show() + } + if (app.profile.isBeforeYear()) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_year_not_started_title) + .setMessage( + R.string.profile_year_not_started_format, + app.profile.dateSemester1Start.formattedString + ) + .setPositiveButton(R.string.ok, null) + .show() + swipeRefreshLayout.isRefreshing = false + return + } + + app.profile.registerName?.let { registerName -> + var status = app.config.sync.registerAvailability[registerName] + if (status == null || status.nextCheck < currentTimeUnix()) { + withContext(Dispatchers.IO) { + val api = SzkolnyApi(app) + api.runCatching(this@MainActivity) { + val availability = getRegisterAvailability() + app.config.sync.registerAvailability = availability + status = availability[registerName] + } + } + } + + if (status?.available != true + || status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) { + swipeRefreshLayout.isRefreshing = false + loadTarget(DRAWER_ITEM_HOME) + if (status != null) + RegisterUnavailableDialog(this, status!!) + return + } + } + swipeRefreshLayout.isRefreshing = true Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show() val fragmentParam = when (navTargetId) { DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection else -> 0 } + val arguments = when (navTargetId) { + DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d) + else -> null + } EdziennikTask.syncProfile( App.profileId, - listOf(navTargetId to fragmentParam) + listOf(navTargetId to fragmentParam), + arguments = arguments ).enqueue(this) } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onUpdateEvent(event: Update) { + EventBus.getDefault().removeStickyEvent(event) + UpdateAvailableDialog(this, event) + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) { + EventBus.getDefault().removeStickyEvent(event) + app.profile.registerName?.let { registerName -> + event.data[registerName]?.let { + RegisterUnavailableDialog(this, it) + } + } + } @Subscribe(threadMode = ThreadMode.MAIN) - fun onSyncStartedEvent(event: ApiTaskStartedEvent) { + fun onApiTaskStartedEvent(event: ApiTaskStartedEvent) { swipeRefreshLayout.isRefreshing = true if (event.profileId == App.profileId) { navView.toolbar.apply { @@ -554,7 +710,14 @@ class MainActivity : AppCompatActivity() { } } @Subscribe(threadMode = ThreadMode.MAIN) - fun onSyncProgressEvent(event: ApiTaskProgressEvent) { + fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) { + d(TAG, "Profile list is empty. Launch LoginActivity.") + app.config.loginFinished = false + startActivity(Intent(this, LoginActivity::class.java)) + finish() + } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) { if (event.profileId == App.profileId) { navView.toolbar.apply { subtitleFormat = null @@ -567,8 +730,9 @@ class MainActivity : AppCompatActivity() { } } } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onSyncProfileFinishedEvent(event: ApiTaskFinishedEvent) { + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + EventBus.getDefault().removeStickyEvent(event) if (event.profileId == App.profileId) { navView.toolbar.apply { subtitleFormat = R.string.toolbar_subtitle @@ -577,22 +741,26 @@ class MainActivity : AppCompatActivity() { } } } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) { + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + EventBus.getDefault().removeStickyEvent(event) swipeRefreshLayout.isRefreshing = false } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onSyncErrorEvent(event: ApiTaskErrorEvent) { + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + EventBus.getDefault().removeStickyEvent(event) navView.toolbar.apply { subtitleFormat = R.string.toolbar_subtitle subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread subtitle = "Gotowe" } + mainSnackbar.dismiss() errorSnackbar.addError(event.error).show() } @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { - if (app.appConfig.dontShowAppManagerDialog) + EventBus.getDefault().removeStickyEvent(event) + if (app.config.sync.dontShowAppManagerDialog) return MaterialAlertDialogBuilder(this) .setTitle(R.string.app_manager_dialog_title) @@ -614,12 +782,15 @@ class MainActivity : AppCompatActivity() { } } .setNeutralButton(R.string.dont_ask_again) { dialog, which -> - app.appConfig.dontShowAppManagerDialog = true - app.saveConfig("dontShowAppManagerDialog") + app.config.sync.dontShowAppManagerDialog = true } .setCancelable(false) .show() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onUserActionRequiredEvent(event: UserActionRequiredEvent) { + app.userActionManager.execute(this, event.profileId, event.type) + } private fun fragmentToSyncName(currentFragment: Int): Int { return when (currentFragment) { @@ -657,26 +828,64 @@ class MainActivity : AppCompatActivity() { } d(TAG, "}") + var intentProfileId = -1 + var intentTargetId = -1 + + if (extras?.containsKey("action") == true) { + val handled = when (extras.getString("action")) { + "serverMessage" -> { + ServerMessageDialog( + this, + extras.getString("serverMessageTitle") ?: getString(R.string.app_name), + extras.getString("serverMessageText") ?: "" + ) + true + } + "feedbackMessage" -> { + intentTargetId = TARGET_FEEDBACK + false + } + "userActionRequired" -> { + app.userActionManager.execute( + this, + extras.getInt("profileId"), + extras.getInt("type") + ) + true + } + "createManualEvent" -> { + val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday() + EventManualDialog( + this, + App.profileId, + defaultDate = date + ) + true + } + else -> false + } + if (handled && !navLoading) { + return + } + } + if (extras?.containsKey("reloadProfileId") == true) { val reloadProfileId = extras.getInt("reloadProfileId", -1) extras.remove("reloadProfileId") - if (reloadProfileId == -1 || (app.profile != null && app.profile.id == reloadProfileId)) { + if (reloadProfileId == -1 || app.profile.id == reloadProfileId) { reloadTarget() return } } - var intentProfileId = -1 - var intentTargetId = -1 - - if (extras?.containsKey("profileId") == true) { + if (extras?.getInt("profileId", -1) != -1) { intentProfileId = extras.getInt("profileId", -1) - extras.remove("profileId") + extras?.remove("profileId") } - if (extras?.containsKey("fragmentId") == true) { + if (extras?.getInt("fragmentId", -1) != -1) { intentTargetId = extras.getInt("fragmentId", -1) - extras.remove("fragmentId") + extras?.remove("fragmentId") } /*if (intentTargetId == -1 && navController.currentDestination?.id == R.id.loadingFragment) { @@ -684,30 +893,33 @@ class MainActivity : AppCompatActivity() { }*/ if (navLoading) { - navLoading = false b.fragment.removeAllViews() if (intentTargetId == -1) intentTargetId = HOME_ID } when { - app.profile == null -> { + app.profile.id == 0 -> { if (intentProfileId == -1) - intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1) - loadProfile(intentProfileId, intentTargetId) + intentProfileId = app.config.lastProfileId + loadProfile(intentProfileId, intentTargetId, extras) } intentProfileId != -1 -> { - loadProfile(intentProfileId, intentTargetId) + if (app.profile.id != intentProfileId) + loadProfile(intentProfileId, intentTargetId, extras) + else + loadTarget(intentTargetId, extras) } intentTargetId != -1 -> { drawer.currentProfile = app.profile.id - if (navTargetId != intentTargetId) + if (navTargetId != intentTargetId || navLoading) loadTarget(intentTargetId, extras) } else -> { drawer.currentProfile = app.profile.id } } + navLoading = false } override fun recreate() { @@ -728,16 +940,32 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } + override fun onStart() { + d(TAG, "Activity started") + super.onStart() + } + override fun onStop() { + d(TAG, "Activity stopped") + super.onStop() + } override fun onResume() { + d(TAG, "Activity resumed") val filter = IntentFilter() filter.addAction(Intent.ACTION_MAIN) registerReceiver(intentReceiver, filter) + EventBus.getDefault().register(this) super.onResume() } override fun onPause() { + d(TAG, "Activity paused") unregisterReceiver(intentReceiver) + EventBus.getDefault().unregister(this) super.onPause() } + override fun onDestroy() { + d(TAG, "Activity destroyed") + super.onDestroy() + } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -751,15 +979,10 @@ class MainActivity : AppCompatActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_LOGIN_ACTIVITY) { - if (resultCode == Activity.RESULT_CANCELED && false) { + if (!app.config.loginFinished) finish() - } else { - if (!app.appConfig.loginFinished) - finish() - else { - handleIntent(data?.extras) - } + handleIntent(data?.extras) } } } @@ -779,38 +1002,58 @@ class MainActivity : AppCompatActivity() { fun loadProfile(id: Int) = loadProfile(id, navTargetId) fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments) - fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { - //d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)") - if (app.profile != null && App.profileId == id) { + fun loadProfile(profile: Profile) = loadProfile( + profile, + navTargetId, + null, + if (app.profile.archived) app.profile.id else null + ) + private fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { + if (App.profileId == id) { drawer.currentProfile = app.profile.id loadTarget(drawerSelection, arguments) return } - AsyncTask.execute { - app.profileLoadById(id) - - this.runOnUiThread { - if (app.profile == null) { - LoginActivity.firstCompleted = false - if (app.appConfig.loginFinished) { - // this shouldn't run - profileListEmptyListener() - } - } else { - setDrawerItems() - drawer.currentProfile = app.profile.id - loadTarget(drawerSelection, arguments) - } - } + val previousArchivedId = if (app.profile.archived) app.profile.id else null + app.profileLoad(id) { + loadProfile(it, drawerSelection, arguments, previousArchivedId) } } + private fun loadProfile(profile: Profile, drawerSelection: Int, arguments: Bundle?, previousArchivedId: Int?) { + App.profile = profile + MessagesFragment.pageSelection = -1 + + setDrawerItems() + + if (previousArchivedId != null) { + // prevents accidentally removing the first item if the archived profile is not shown + drawer.removeProfileById(previousArchivedId) + } + if (profile.archived) { + drawer.prependProfile(Profile( + id = profile.id, + loginStoreId = profile.loginStoreId, + loginStoreType = profile.loginStoreType, + name = profile.name, + subname = "Archiwum - ${profile.subname}" + ).also { + it.archived = true + }) + } + + // the drawer profile is updated automatically when the drawer item is clicked + // update it manually when switching profiles from other source + //if (drawer.currentProfile != app.profile.id) + drawer.currentProfile = app.profileId + loadTarget(drawerSelection, arguments) + } fun loadTarget(id: Int, arguments: Bundle? = null) { var loadId = id if (loadId == -1) { loadId = DRAWER_ITEM_HOME } val target = navTargetList - .singleOrNull { it.id == loadId } + .firstOrNull { it.id == loadId } if (target == null) { Toast.makeText(this, getString(R.string.error_invalid_fragment, id), Toast.LENGTH_LONG).show() loadTarget(navTargetList.first(), arguments) @@ -819,15 +1062,16 @@ class MainActivity : AppCompatActivity() { loadTarget(target, arguments) } } - private fun loadTarget(target: NavTarget, arguments: Bundle? = null) { - d("NavDebug", "loadItem(id = ${target.id})") + private fun loadTarget(target: NavTarget, args: Bundle? = null) { + d("NavDebug", "loadTarget(target = $target, args = $args)") + val arguments = args ?: navBackStack.firstOrNull { it.first.id == target.id }?.second ?: Bundle() bottomSheet.close() bottomSheet.removeAllContextual() bottomSheet.toggleGroupEnabled = false - bottomSheet.onCloseListener = null drawer.close() - drawer.setSelection(target.id, fireOnClick = false) + if (drawer.getSelection() != target.id) + drawer.setSelection(target.id, fireOnClick = false) navView.toolbar.setTitle(target.title ?: target.name) navView.bottomBar.fabEnable = false navView.bottomBar.fabExtended = false @@ -847,7 +1091,7 @@ class MainActivity : AppCompatActivity() { ) } else { - navBackStack.lastIndexOf(target).let { + navBackStack.keys().lastIndexOf(target).let { if (it == -1) return@let target // pop the back stack up until that target @@ -869,6 +1113,7 @@ class MainActivity : AppCompatActivity() { navBackStack.removeAt(navBackStack.lastIndex) } navTarget = target + navArguments = arguments return@let null }?.let { @@ -878,8 +1123,9 @@ class MainActivity : AppCompatActivity() { R.anim.task_open_enter, R.anim.task_open_exit ) - navBackStack.add(navTarget) + navBackStack.add(navTarget to navArguments) navTarget = target + navArguments = arguments } } @@ -894,7 +1140,7 @@ class MainActivity : AppCompatActivity() { d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:") navBackStack.forEachIndexed { index, target2 -> - d("NavDebug", " - $index: ${target2.fragmentClass?.java?.simpleName}") + d("NavDebug", " - $index: ${target2.first.fragmentClass?.java?.simpleName}") } transaction.replace(R.id.fragment, fragment) @@ -919,11 +1165,18 @@ class MainActivity : AppCompatActivity() { return false } // TODO back stack argument support - if (navTarget.popToHome) { - loadTarget(HOME_ID) - } - else { - loadTarget(navBackStack.last()) + when { + navTarget.popToHome -> { + loadTarget(HOME_ID) + } + navTarget.popTo != null -> { + loadTarget(navTarget.popTo ?: HOME_ID) + } + else -> { + navBackStack.last().let { + loadTarget(it.first, it.second) + } + } } return true } @@ -938,6 +1191,8 @@ class MainActivity : AppCompatActivity() { * that something has changed in the bottom sheet. */ fun gainAttention() { + if (app.config.ui.bottomSheetOpened) + return b.navView.postDelayed({ navView.gainAttentionOnBottomBar() }, 2000) @@ -965,11 +1220,12 @@ class MainActivity : AppCompatActivity() { val item = DrawerPrimaryItem() .withIdentifier(target.id.toLong()) .withName(target.name) - .withHiddenInMiniDrawer(!app.appConfig.miniDrawerButtonIds.contains(target.id)) + .withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id)) .also { if (target.description != null) it.withDescription(target.description!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) } .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) } .also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)} + .withSelectedBackgroundAnimated(false) if (target.badgeTypeId != null) drawer.addUnreadCounterType(target.badgeTypeId!!, target.id) @@ -985,12 +1241,11 @@ class MainActivity : AppCompatActivity() { } fun setDrawerItems() { - d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}") + d("NavDebug", "setDrawerItems() app.profile = ${app.profile}") val drawerItems = arrayListOf>() val drawerProfiles = arrayListOf() - val supportedFragments = if (app.profile == null) arrayListOf() - else app.profile.supportedFragments + val supportedFragments = app.profile.supportedFragments targetPopToHomeList.clear() @@ -1030,7 +1285,7 @@ class MainActivity : AppCompatActivity() { drawer.addProfileSettings(*drawerProfiles.toTypedArray()) } - private fun showProfileContextMenu(profile: IProfile<*>, view: View) { + private fun showProfileContextMenu(profile: IProfile, view: View) { val profileId = profile.identifier.toInt() val popupMenu = PopupMenu(this, view) popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings) @@ -1043,7 +1298,7 @@ class MainActivity : AppCompatActivity() { } loadTarget(DRAWER_ITEM_SETTINGS, null) } else if (item.itemId == 2) { - ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?") + ProfileRemoveDialog(this, profileId, profile.name?.getText(this) ?: "?") } true } @@ -1054,30 +1309,16 @@ class MainActivity : AppCompatActivity() { private var targetHomeId: Int = -1 override fun onBackPressed() { if (!b.navView.onBackPressed()) { - - navigateUp() - - /*val currentDestinationId = navController.currentDestination?.id - - if (if (targetHomeId != -1 && targetPopToHomeList.contains(navController.currentDestination?.id)) { - if (!navController.popBackStack(targetHomeId, false)) { - navController.navigateUp() - } - true - } else { - navController.navigateUp() - }) { - val currentId = navController.currentDestination?.id ?: -1 - val drawerSelection = navTargetList - .singleOrNull { - it.navGraphId == currentId - }?.also { - navView.toolbar.setTitle(it.title ?: it.name) - }?.id ?: -1 - drawer.setSelection(drawerSelection, false) + if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome) + || navTarget.id == DRAWER_ITEM_HOME)) { + b.navView.drawer.toggle() } else { - super.onBackPressed() - }*/ + navigateUp() + } } } + + fun error(error: ApiError) = errorSnackbar.addError(error).show() + fun snackbar(text: String, actionText: String? = null, onClick: (() -> Unit)? = null) = mainSnackbar.snackbar(text, actionText, onClick) + fun snackbarDismiss() = mainSnackbar.dismiss() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java b/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java deleted file mode 100644 index 660647b9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java +++ /dev/null @@ -1,352 +0,0 @@ -package pl.szczodrzynski.edziennik; - -import android.app.IntentService; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.util.Log; - -import androidx.core.app.NotificationCompat; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull; -import pl.szczodrzynski.edziennik.receivers.BootReceiver; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; - -import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT; -import static androidx.core.app.NotificationCompat.PRIORITY_MAX; - -public class Notifier { - - private static final String TAG = "Notifier"; - public static final int ID_GET_DATA = 1337000; - public static final int ID_GET_DATA_ERROR = 1337001; - private static String CHANNEL_GET_DATA_NAME; - private static String CHANNEL_GET_DATA_DESC; - private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA"; - - public static final int ID_NOTIFICATIONS = 1337002; - public static String CHANNEL_NOTIFICATIONS_NAME; - public static String CHANNEL_NOTIFICATIONS_DESC; - public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS"; - - public static final int ID_NOTIFICATIONS_QUIET = 1337002; - public static String CHANNEL_NOTIFICATIONS_QUIET_NAME; - public static String CHANNEL_NOTIFICATIONS_QUIET_DESC; - public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET"; - - private static final int ID_UPDATES = 1337003; - private static String CHANNEL_UPDATES_NAME; - private static String CHANNEL_UPDATES_DESC; - private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES"; - - private App app; - public NotificationManager notificationManager; - private NotificationCompat.Builder getDataNotificationBuilder; - public int notificationColor; - - Notifier(App _app) { - this.app = _app; - - CHANNEL_GET_DATA_NAME = app.getString(R.string.notification_channel_get_data_name); - CHANNEL_GET_DATA_DESC = app.getString(R.string.notification_channel_get_data_desc); - CHANNEL_NOTIFICATIONS_NAME = app.getString(R.string.notification_channel_notifications_name); - CHANNEL_NOTIFICATIONS_DESC = app.getString(R.string.notification_channel_notifications_desc); - CHANNEL_NOTIFICATIONS_QUIET_NAME = app.getString(R.string.notification_channel_notifications_quiet_name); - CHANNEL_NOTIFICATIONS_QUIET_DESC = app.getString(R.string.notification_channel_notifications_quiet_desc); - CHANNEL_UPDATES_NAME = app.getString(R.string.notification_channel_updates_name); - CHANNEL_UPDATES_DESC = app.getString(R.string.notification_channel_updates_desc); - - notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary); - notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN); - channelGetData.setDescription(CHANNEL_GET_DATA_DESC); - notificationManager.createNotificationChannel(channelGetData); - - NotificationChannel channelNotifications = new NotificationChannel(GROUP_KEY_NOTIFICATIONS, CHANNEL_NOTIFICATIONS_NAME, NotificationManager.IMPORTANCE_HIGH); - channelNotifications.setDescription(CHANNEL_NOTIFICATIONS_DESC); - channelNotifications.enableLights(true); - channelNotifications.setLightColor(notificationColor); - notificationManager.createNotificationChannel(channelNotifications); - - NotificationChannel channelNotificationsQuiet = new NotificationChannel(GROUP_KEY_NOTIFICATIONS_QUIET, CHANNEL_NOTIFICATIONS_QUIET_NAME, NotificationManager.IMPORTANCE_DEFAULT); - channelNotificationsQuiet.setDescription(CHANNEL_NOTIFICATIONS_QUIET_DESC); - channelNotificationsQuiet.setSound(null, null); - channelNotificationsQuiet.enableVibration(false); - notificationManager.createNotificationChannel(channelNotificationsQuiet); - - NotificationChannel channelUpdates = new NotificationChannel(GROUP_KEY_UPDATES, CHANNEL_UPDATES_NAME, NotificationManager.IMPORTANCE_HIGH); - channelUpdates.setDescription(CHANNEL_UPDATES_DESC); - notificationManager.createNotificationChannel(channelUpdates); - } - } - - public boolean shouldBeQuiet() { - long now = Time.getNow().getInMillis(); - long start = app.appConfig.quietHoursStart; - long end = app.appConfig.quietHoursEnd; - if (start > end) { - end += 1000 * 60 * 60 * 24; - //Log.d(TAG, "Night passing"); - } - if (start > now) { - now += 1000 * 60 * 60 * 24; - //Log.d(TAG, "Now is smaller"); - } - //Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end); - return app.appConfig.quietHoursStart > 0 && now >= start && now <= end; - } - - public int getNotificationDefaults() { - return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL); - } - public String getNotificationGroup() { - return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS; - } - public int getNotificationPriority() { - return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX; - } - - /* _____ _ _____ _ - | __ \ | | / ____| | | - | | | | __ _| |_ __ _ | | __ ___| |_ - | | | |/ _` | __/ _` | | | |_ |/ _ \ __| - | |__| | (_| | || (_| | | |__| | __/ |_ - |_____/ \__,_|\__\__,_| \_____|\___|\_*/ - public Notification notificationGetDataShow(int maxProgress) { - /*Intent notificationIntent = new Intent(app.getContext(), SyncService.class); - notificationIntent.setAction(ACTION_CANCEL); - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);*/ - - getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_get_data_title)) - .setContentText(app.getString(R.string.notification_get_data_text)) - //.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent) - //.setGroup(GROUP_KEY_GET_DATA) - .setOngoing(true) - .setProgress(maxProgress, 0, false) - .setTicker(app.getString(R.string.notification_get_data_summary)) - .setPriority(NotificationCompat.PRIORITY_LOW); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataProgress(int progress, int maxProgress) { - getDataNotificationBuilder.setProgress(maxProgress, progress, false); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataAction(int stringResId) { - getDataNotificationBuilder.setContentTitle(app.getString(R.string.sync_action_format, app.getString(stringResId))); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataProfile(String profileName) { - getDataNotificationBuilder.setContentText(profileName); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataError(String profileName, String error, int failedProfileId) { - Intent notificationIntent = new Intent(app.getContext(), Notifier.GetDataRetryService.class); - notificationIntent.putExtra("failedProfileId", failedProfileId); - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - getDataNotificationBuilder.mActions.clear(); - /*try { - //Use reflection clean up old actions - Field f = getDataNotificationBuilder.getClass().getDeclaredField("mActions"); - f.setAccessible(true); - f.set(getDataNotificationBuilder, new ArrayList()); - } catch (Exception e) { - e.printStackTrace(); - }*/ - - getDataNotificationBuilder.setProgress(0, 0, false) - .setTicker(app.getString(R.string.notification_get_data_error_summary)) - .setSmallIcon(android.R.drawable.stat_sys_warning) - .addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_once_again), pendingIntent) - .setContentTitle(app.getString(R.string.notification_get_data_error_title, profileName)) - .setContentText(error) - .setStyle(new NotificationCompat.BigTextStyle().bigText(error)) - .setOngoing(false); - return getDataNotificationBuilder.build(); - } - - public void notificationPost(int id, Notification notification) { - notificationManager.notify(id, notification); - } - - public void notificationCancel(int id) { - notificationManager.cancel(id); - } - - //public void notificationGetDataHide() { - // notificationManager.cancel(ID_GET_DATA); - // } - - public static class GetDataRetryService extends IntentService { - private static final String TAG = "Notifier/GetDataRetry"; - - public GetDataRetryService() { - super(Notifier.GetDataRetryService.class.getSimpleName()); - } - - @Override - protected void onHandleIntent(Intent intent) { - - - } - } - - /* _ _ _ _ __ _ _ _ - | \ | | | | (_)/ _(_) | | (_) - | \| | ___ | |_ _| |_ _ ___ __ _| |_ _ ___ _ __ - | . ` |/ _ \| __| | _| |/ __/ _` | __| |/ _ \| '_ \ - | |\ | (_) | |_| | | | | (_| (_| | |_| | (_) | | | | - |_| \_|\___/ \__|_|_| |_|\___\__,_|\__|_|\___/|_| |*/ - public void add(pl.szczodrzynski.edziennik.utils.models.Notification notification) { - app.appConfig.notifications.add(notification); - } - - public void postAll(ProfileFull profile) { - Collections.sort(app.appConfig.notifications, (o1, o2) -> (o2.addedDate - o1.addedDate > 0) ? 1 : (o2.addedDate - o1.addedDate < 0) ? -1 : 0); - if (profile != null && !profile.getSyncNotifications()) - return; - - if (app.appConfig.notifications.size() > 40) { - app.appConfig.notifications.subList(40, app.appConfig.notifications.size() - 1).clear(); - } - - int unreadCount = 0; - List notificationList = new ArrayList<>(); - for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) { - if (!notification.notified) { - notification.seen = false; - notification.notified = true; - unreadCount++; - if (notificationList.size() < 10) { - notificationList.add(notification); - } - } - else { - notification.seen = true; - } - } - - for (pl.szczodrzynski.edziennik.utils.models.Notification notification: notificationList) { - Intent intent = new Intent(app, MainActivity.class); - notification.fillIntent(intent); - PendingIntent pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0); - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, getNotificationGroup()) - // title, text, type, date - .setContentTitle(notification.title) - .setContentText(notification.text) - .setSubText(pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type)) - .setWhen(notification.addedDate) - .setTicker(app.getString(R.string.notification_ticker_format, pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type))) - // icon, color, lights, priority - .setSmallIcon(R.drawable.ic_notification) - .setColor(notificationColor) - .setLights(0xFF00FFFF, 2000, 2000) - .setPriority(getNotificationPriority()) - // channel, group, style - .setChannelId(getNotificationGroup()) - .setGroup(getNotificationGroup()) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - .setStyle(new NotificationCompat.BigTextStyle().bigText(notification.text)) - // intent, auto cancel - .setContentIntent(pendingIntent) - .setAutoCancel(true); - if (!shouldBeQuiet()) { - notificationBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(notification.id, notificationBuilder.build()); - } - - if (notificationList.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - Intent intent = new Intent(app, MainActivity.class); - intent.setAction("android.intent.action.MAIN"); - intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS); - PendingIntent pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS, - intent, 0); - - NotificationCompat.Builder groupBuilder = - new NotificationCompat.Builder(app, getNotificationGroup()) - .setSmallIcon(R.drawable.ic_notification) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount)) - .setGroupSummary(true) - .setAutoCancel(true) - .setChannelId(getNotificationGroup()) - .setGroup(getNotificationGroup()) - .setLights(0xFF00FFFF, 2000, 2000) - .setPriority(getNotificationPriority()) - .setContentIntent(pendingIntent) - .setStyle(new NotificationCompat.BigTextStyle()); - if (!shouldBeQuiet()) { - groupBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build()); - } - } - - /* _ _ _ _ - | | | | | | | | - | | | |_ __ __| | __ _| |_ ___ ___ - | | | | '_ \ / _` |/ _` | __/ _ \/ __| - | |__| | |_) | (_| | (_| | || __/\__ \ - \____/| .__/ \__,_|\__,_|\__\___||___/ - | | - |*/ - public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) { - if (!app.appConfig.notifyAboutUpdates) - return; - Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class) - .putExtra("update_version", updateVersion) - .putExtra("update_url", updateUrl) - .putExtra("update_filename", updateFilename); - - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_UPDATES) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_updates_title)) - .setContentText(app.getString(R.string.notification_updates_text, updateVersion)) - .setLights(0xFF00FFFF, 2000, 2000) - .setContentIntent(pendingIntent) - .setTicker(app.getString(R.string.notification_updates_summary)) - .setPriority(PRIORITY_MAX) - .setAutoCancel(true); - if (!shouldBeQuiet()) { - notificationBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(ID_UPDATES, notificationBuilder.build()); - } - - public void notificationUpdatesHide() { - if (!app.appConfig.notifyAboutUpdates) - return; - notificationManager.cancel(ID_UPDATES); - } - - public void dump() { - for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) { - Log.d(TAG, "Profile"+notification.profileId+" Notification from "+ Date.fromMillis(notification.addedDate).getFormattedString()+" "+ Time.fromMillis(notification.addedDate).getStringHMS()+" - "+notification.text); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/DataNotifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/DataNotifications.kt deleted file mode 100644 index 73f6de4f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/DataNotifications.kt +++ /dev/null @@ -1,210 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2 - -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.models.Data -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.* -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_LUCKY_NUMBER -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ANNOUNCEMENT -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ATTENDANCE -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_EVENT -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_GRADE -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_MESSAGE -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_NOTICE -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE -import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle -import pl.szczodrzynski.edziennik.utils.models.Date - -class DataNotifications(val data: Data) { - companion object { - private const val TAG = "DataNotifications" - } - - val app = data.app - val profileId = data.profile?.id ?: -1 - val profileName = data.profile?.name ?: "" - val profile = data.profile - val loginStore = data.loginStore - - init { run { - if (profile == null) { - return@run - } - - for (change in app.db.lessonChangeDao().getNotNotifiedNow(profileId)) { - val text = app.getString(R.string.notification_lesson_change_format, change.changeTypeStr(app), if (change.lessonDate == null) "" else change.lessonDate!!.formattedString, change.subjectLongName) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE), - text = text, - type = TYPE_TIMETABLE_LESSON_CHANGE, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_TIMETABLE, - addedDate = change.addedDate - ).addExtra("timetableDate", change.lessonDate?.value?.toLong()) - } - - for (event in app.db.eventDao().getNotNotifiedNow(profileId)) { - val text = if (event.type == Event.TYPE_HOMEWORK) - app.getString( - if (event.subjectLongName.isNullOrEmpty()) - R.string.notification_homework_no_subject_format - else - R.string.notification_homework_format, - event.subjectLongName, - event.eventDate.formattedString - ) - else - app.getString( - if (event.subjectLongName.isNullOrEmpty()) - R.string.notification_event_no_subject_format - else - R.string.notification_event_format, - event.typeName, - event.eventDate.formattedString, - event.subjectLongName - ) - val type = if (event.type == Event.TYPE_HOMEWORK) TYPE_NEW_HOMEWORK else TYPE_NEW_EVENT - data.notifications += Notification( - title = app.getNotificationTitle(type), - text = text, - type = type, - profileId = profileId, - profileName = profileName, - viewId = if (event.type == Event.TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA, - addedDate = event.addedDate - ).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) - } - - val today = Date.getToday() - val todayValue = today.value - profile.currentSemester = profile.dateToSemester(today) - - for (grade in app.db.gradeDao().getNotNotifiedNow(profileId)) { - val gradeName = when (grade.type) { - TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name) - TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name) - TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name) - TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name) - else -> grade.name - } - val text = app.getString(R.string.notification_grade_format, gradeName, grade.subjectLongName) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_NEW_GRADE), - text = text, - type = TYPE_NEW_GRADE, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_GRADES, - addedDate = grade.addedDate - ).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId) - } - - for (notice in app.db.noticeDao().getNotNotifiedNow(profileId)) { - val noticeTypeStr = if (notice.type == Notice.TYPE_POSITIVE) app.getString(R.string.notification_notice_praise) else if (notice.type == Notice.TYPE_NEGATIVE) app.getString(R.string.notification_notice_warning) else app.getString(R.string.notification_notice_new) - val text = app.getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).formattedString) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_NEW_NOTICE), - text = text, - type = TYPE_NEW_NOTICE, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_BEHAVIOUR, - addedDate = notice.addedDate - ).addExtra("noticeId", notice.id) - } - - for (attendance in app.db.attendanceDao().getNotNotifiedNow(profileId)) { - var attendanceTypeStr = app.getString(R.string.notification_type_attendance) - when (attendance.type) { - Attendance.TYPE_ABSENT -> attendanceTypeStr = app.getString(R.string.notification_absence) - Attendance.TYPE_ABSENT_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_absence_excused) - Attendance.TYPE_BELATED -> attendanceTypeStr = app.getString(R.string.notification_belated) - Attendance.TYPE_BELATED_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_belated_excused) - Attendance.TYPE_RELEASED -> attendanceTypeStr = app.getString(R.string.notification_release) - } - val text = app.getString( - if (attendance.subjectLongName.isNullOrEmpty()) - R.string.notification_attendance_no_lesson_format - else - R.string.notification_attendance_format, - attendanceTypeStr, - attendance.subjectLongName, - attendance.lessonDate.formattedString - ) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_NEW_ATTENDANCE), - text = text, - type = TYPE_NEW_ATTENDANCE, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_ATTENDANCE, - addedDate = attendance.addedDate - ).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId) - } - - for (announcement in app.db.announcementDao().getNotNotifiedNow(profileId)) { - val text = app.context.getString(R.string.notification_announcement_format, announcement.subject) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_NEW_ANNOUNCEMENT), - text = text, - type = TYPE_NEW_ANNOUNCEMENT, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_ANNOUNCEMENTS, - addedDate = announcement.addedDate - ).addExtra("announcementId", announcement.id) - } - - for (message in app.db.messageDao().getReceivedNotNotifiedNow(profileId)) { - val text = app.context.getString(R.string.notification_message_format, message.senderFullName, message.subject) - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_NEW_MESSAGE), - text = text, - type = TYPE_NEW_MESSAGE, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_MESSAGES, - addedDate = message.addedDate - ).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id) - } - - val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId) - luckyNumbers?.removeAll { it.date < today } - luckyNumbers?.forEach { luckyNumber -> - val text = when { - luckyNumber.date.value == todayValue -> // LN for today - app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number) - luckyNumber.date.value == todayValue + 1 -> // LN for tomorrow - app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number) - else -> // LN for later - app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number) - } - data.notifications += Notification( - title = app.getNotificationTitle(TYPE_LUCKY_NUMBER), - text = text, - type = TYPE_LUCKY_NUMBER, - profileId = profileId, - profileName = profileName, - viewId = DRAWER_ITEM_HOME, - addedDate = luckyNumber.addedDate - ) - } - - data.db.metadataDao().setAllNotified(profileId, true) - }} -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt deleted file mode 100644 index e6025f9d..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-6. - */ - -package pl.szczodrzynski.edziennik.api.v2 - -object Regexes { - val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy { - """\n*\s*(.+?)\s*\n*(?:<.*?)??""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_COLOR by lazy { - """background-color:([#A-Fa-f0-9]+);""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_CATEGORY by lazy { - """> (.+?):""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy { - """Średnia ocen:.*([0-9]*\.?[0-9]*)""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy { - """Wpisano:.*.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy { - """Liczona do średniej:.*?nie
""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_GRADES_DETAILS by lazy { - """(.+?).*?.+?.*?\((.+?)\).*?.*?Wartość oceny:.*?([0-9.]+).*?Wpisał\(a\):.*?(.+?)""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - - val MOBIDZIENNIK_EVENT_TYPE by lazy { - """\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_LUCKY_NUMBER by lazy { - """class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val MOBIDZIENNIK_CLASS_CALENDAR by lazy { - """events: (.+),$""".toRegex(RegexOption.MULTILINE) - } - - - - val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { - """""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val IDZIENNIK_LOGIN_ERROR by lazy { - """id="spanErrorMessage">(.*?)(.+?)""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { - """id="ctl00_CzyRodzic" value="([01])" />""".toRegex() - } - val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { - """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { - """""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { - """(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)""".toRegex(RegexOption.DOT_MATCHES_ALL) - } - - val VULCAN_SHITFT_ANNOTATION by lazy { - """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ServerSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ServerSync.kt deleted file mode 100644 index 7114501a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ServerSync.kt +++ /dev/null @@ -1,179 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2 - -import pl.szczodrzynski.edziennik.App.APP_URL -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.models.Data -import pl.szczodrzynski.edziennik.data.api.AppError.CODE_APP_SERVER_ERROR -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_EVENT -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_SERVER_MESSAGE -import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.getJsonArray -import pl.szczodrzynski.edziennik.getLong -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.network.ServerRequest - -class ServerSync(val data: Data, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "ServerSync" - } - - val app = data.app - val profileId = data.profile?.id ?: -1 - val profileName = data.profile?.name ?: "" - val profile = data.profile - val loginStore = data.loginStore - - private fun getUsernameId(): String { - if (loginStore.data == null) { - return "NO_LOGIN_STORE" - } - if (profile?.studentData == null) { - return "NO_STUDENT_STORE" - } - return when (data.loginStore.type) { - LoginStore.LOGIN_TYPE_MOBIDZIENNIK -> loginStore.getLoginData("serverName", "MOBI_UN") + ":" + loginStore.getLoginData("username", "MOBI_UN") + ":" + profile.getStudentData("studentId", -1) - LoginStore.LOGIN_TYPE_LIBRUS -> profile.getStudentData("schoolName", "LIBRUS_UN") + ":" + profile.getStudentData("accountLogin", "LIBRUS_LOGIN_UN") - LoginStore.LOGIN_TYPE_IUCZNIOWIE -> loginStore.getLoginData("schoolName", "IUCZNIOWIE_UN") + ":" + loginStore.getLoginData("username", "IUCZNIOWIE_UN") + ":" + profile.getStudentData("registerId", -1) - LoginStore.LOGIN_TYPE_VULCAN -> profile.getStudentData("schoolName", "VULCAN_UN") + ":" + profile.getStudentData("studentId", -1) - LoginStore.LOGIN_TYPE_DEMO -> loginStore.getLoginData("serverName", "DEMO_UN") + ":" + loginStore.getLoginData("username", "DEMO_UN") + ":" + profile.getStudentData("studentId", -1) - else -> "TYPE_UNKNOWN" - } - } - - init { run { - if (profile?.registration != Profile.REGISTRATION_ENABLED) { - onSuccess() - return@run - } - - val request = ServerRequest( - app, - app.requestScheme+APP_URL+"main.php?sync", - "Edziennik2/REG", - profile, - data.loginStore.type, - getUsernameId() - ) - - if (profile.empty) { - request.setBodyParameter("first_run", "true") - } - - var hasNotifications = true - if (app.appConfig.webPushEnabled) { - data.notifications - .filterNot { it.posted } - .let { - if (it.isEmpty()) { - hasNotifications = false - null - } - else - it - }?.forEachIndexed { index, notification -> - if (notification.type != TYPE_NEW_SHARED_EVENT - && notification.type != TYPE_SERVER_MESSAGE - && notification.type != TYPE_NEW_SHARED_HOMEWORK) { - request.setBodyParameter("notify[$index][type]", notification.type.toString()) - request.setBodyParameter("notify[$index][title]", notification.title) - request.setBodyParameter("notify[$index][text]", notification.text) - } - } - } - - if ((!app.appConfig.webPushEnabled || !hasNotifications) && !profile.enableSharedEvents) { - onSuccess() - return@run - } - - val result = request.runSync() - - if (result == null) { - data.error(ApiError(TAG, CODE_APP_SERVER_ERROR) - .setCritical(false)) - onSuccess() - return@run - } - var apiResponse = result.toString() - if (result.getString("success") != "true") { - data.error(ApiError(TAG, CODE_APP_SERVER_ERROR) - .setCritical(false)) - onSuccess() - return@run - } - // HERE PROCESS ALL THE RECEIVED EVENTS - // add them to the profile and create appropriate notifications - result.getJsonArray("events")?.forEach { jEventEl -> - val event = jEventEl.asJsonObject - val teamCode = event.getString("team") - - // get the target Team from teamCode - val team = app.db.teamDao().getByCodeNow(profile.id, teamCode) - if (team != null) { - - // create the event from Json. Add the missing teamId and !!profileId!! - val eventObject = app.gson.fromJson(event.toString(), Event::class.java) - // proguard. disable for Event.class - if (eventObject.eventDate == null) { - apiResponse += "\n\nEventDate == null\n$event" - } - eventObject.profileId = profileId - eventObject.teamId = team.id - eventObject.addedManually = true - - if (eventObject.sharedBy == getUsernameId()) { - eventObject.sharedBy = "self" - eventObject.sharedByName = profile.studentNameLong - } - - val typeObject = app.db.eventTypeDao().getByIdNow(profileId, eventObject.type) - - app.db.eventDao().add(eventObject) - - val metadata = Metadata( - profileId, - if (eventObject.type == TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, - eventObject.id, - profile.empty, - true, - event.getLong("addedDate") ?: 0 - ) - - val metadataId = app.db.metadataDao().add(metadata) - - // notify if the event is new and not first sync - if (metadataId != -1L && !profile.empty) { - val text = app.getString( - R.string.notification_shared_event_format, - eventObject.sharedByName, - if (typeObject != null) typeObject.name else "wydarzenie", - if (eventObject.eventDate == null) "???" else eventObject.eventDate.formattedString, - eventObject.topic - ) - val type = if (eventObject.type == TYPE_HOMEWORK) TYPE_NEW_SHARED_HOMEWORK else TYPE_NEW_SHARED_EVENT - data.notifications += Notification( - title = app.getNotificationTitle(type), - text = text, - type = type, - profileId = profileId, - profileName = profileName, - viewId = if (eventObject.type == TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA, - addedDate = metadata.addedDate - ).addExtra("eventId", eventObject.id).addExtra("eventDate", eventObject.eventDate.value.toLong()) - } - } - } - - onSuccess() - }} -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskAllFinishedEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskAllFinishedEvent.kt deleted file mode 100644 index 1bef5708..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskAllFinishedEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.events - -class ApiTaskAllFinishedEvent \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskErrorEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskErrorEvent.kt deleted file mode 100644 index 5b76dfe5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskErrorEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.events - -import pl.szczodrzynski.edziennik.api.v2.models.ApiError - -class ApiTaskErrorEvent(val error: ApiError) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskFinishedEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskFinishedEvent.kt deleted file mode 100644 index 546fe3ae..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskFinishedEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.events - -class ApiTaskFinishedEvent(val profileId: Int) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskStartedEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskStartedEvent.kt deleted file mode 100644 index b8af894e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/ApiTaskStartedEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.events - -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile - -class ApiTaskStartedEvent(val profileId: Int, val profile: Profile? = null) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/FirstLoginFinishedEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/FirstLoginFinishedEvent.kt deleted file mode 100644 index 31fba7c9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/FirstLoginFinishedEvent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.events - -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile - -data class FirstLoginFinishedEvent(val profileList: List, val loginStore: LoginStore) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/MessageGetEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/MessageGetEvent.kt deleted file mode 100644 index 353667ac..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/MessageGetEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-12. - */ - -package pl.szczodrzynski.edziennik.api.v2.events - -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull - -data class MessageGetEvent(val message: MessageFull) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/ServiceCloseRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/ServiceCloseRequest.kt deleted file mode 100644 index a859301c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/ServiceCloseRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-1. - */ - -package pl.szczodrzynski.edziennik.api.v2.events.requests - -class ServiceCloseRequest \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/TaskCancelRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/TaskCancelRequest.kt deleted file mode 100644 index f40a9455..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/requests/TaskCancelRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-1. - */ - -package pl.szczodrzynski.edziennik.api.v2.events.requests - -class TaskCancelRequest(val taskId: Int) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/EdziennikTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/EdziennikTask.kt deleted file mode 100644 index fa2d513f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/EdziennikTask.kt +++ /dev/null @@ -1,93 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.events.task - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.idziennik.Idziennik -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface -import pl.szczodrzynski.edziennik.api.v2.librus.Librus -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.Mobidziennik -import pl.szczodrzynski.edziennik.api.v2.template.Template -import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull - -open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) { - companion object { - private const val TAG = "EdziennikTask" - - fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore)) - fun sync() = EdziennikTask(-1, SyncRequest()) - fun syncProfile(profileId: Int, viewIds: List>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, arguments)) - fun syncProfileList(profileList: List) = EdziennikTask(-1, SyncProfileListRequest(profileList)) - fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message)) - fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) - } - - private lateinit var loginStore: LoginStore - - override fun prepare(app: App) { - if (request is FirstLoginRequest) { - // get the requested profile and login store - this.profile = null - loginStore = request.loginStore - // save the profile ID and name as the current task's - taskName = app.getString(R.string.edziennik_notification_api_first_login_title) - } - else { - // get the requested profile and login store - val profile = app.db.profileDao().getByIdNow(profileId) - this.profile = profile - if (profile == null) { - return - } - val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return - this.loginStore = loginStore - // save the profile ID and name as the current task's - taskName = app.getString(R.string.edziennik_notification_api_sync_title_format, profile.name) - } - } - - private var edziennikInterface: EdziennikInterface? = null - - internal fun run(app: App, taskCallback: EdziennikCallback) { - edziennikInterface = when (loginStore.type) { - LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) - LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) - LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) - LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback) - LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) - else -> null - } - if (edziennikInterface == null) { - return - } - - when (request) { - is SyncProfileRequest -> edziennikInterface?.sync( - featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } ?: Features.getAllIds(), - viewId = request.viewIds?.get(0)?.first, - arguments = request.arguments) - is MessageGetRequest -> edziennikInterface?.getMessage(request.message) - is FirstLoginRequest -> edziennikInterface?.firstLogin() - is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead() - } - } - - override fun cancel() { - edziennikInterface?.cancel() - } - - override fun toString(): String { - return "EdziennikTask(profileId=$profileId, request=$request, edziennikInterface=$edziennikInterface)" - } - - data class FirstLoginRequest(val loginStore: LoginStore) - class SyncRequest - data class SyncProfileRequest(val viewIds: List>? = null, val arguments: JsonObject? = null) - data class SyncProfileListRequest(val profileList: List) - data class MessageGetRequest(val message: MessageFull) - class AnnouncementsReadRequest -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/NotifyTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/NotifyTask.kt deleted file mode 100644 index edd7a6fa..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/events/task/NotifyTask.kt +++ /dev/null @@ -1,89 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.events.task - -import android.app.PendingIntent -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.utils.models.Notification -import kotlin.math.min - -class NotifyTask : IApiTask(-1) { - override fun prepare(app: App) { - taskName = app.getString(R.string.edziennik_notification_api_notify_title) - } - - override fun cancel() { - - } - - fun run(app: App, taskCallback: EdziennikCallback) { - val list = app.db.notificationDao().getNotPostedNow() - val notificationList = list.subList(0, min(15, list.size)) - - var unreadCount = list.size - - for (notification in notificationList) { - val intent = Intent(app, MainActivity::class.java) - notification.fillIntent(intent) - val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0) - val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup) - // title, text, type, date - .setContentTitle(notification.title) - .setContentText(notification.text) - .setSubText(Notification.stringType(app, notification.type)) - .setWhen(notification.addedDate) - .setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type))) - // icon, color, lights, priority - .setSmallIcon(R.drawable.ic_notification) - .setColor(app.notifier.notificationColor) - .setLights(-0xff0001, 2000, 2000) - .setPriority(app.notifier.notificationPriority) - // channel, group, style - .setChannelId(app.notifier.notificationGroup) - .setGroup(app.notifier.notificationGroup) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - .setStyle(NotificationCompat.BigTextStyle().bigText(notification.text)) - // intent, auto cancel - .setContentIntent(pendingIntent) - .setAutoCancel(true) - if (!app.notifier.shouldBeQuiet()) { - notificationBuilder.setDefaults(app.notifier.notificationDefaults) - } - app.notifier.notificationManager.notify(notification.id, notificationBuilder.build()) - } - - if (notificationList.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val intent = Intent(app, MainActivity::class.java) - intent.action = "android.intent.action.MAIN" - intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS) - val pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS, - intent, 0) - - val groupBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup) - .setSmallIcon(R.drawable.ic_notification) - .setColor(app.notifier.notificationColor) - .setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount)) - .setGroupSummary(true) - .setAutoCancel(true) - .setChannelId(app.notifier.notificationGroup) - .setGroup(app.notifier.notificationGroup) - .setLights(-0xff0001, 2000, 2000) - .setPriority(app.notifier.notificationPriority) - .setContentIntent(pendingIntent) - .setStyle(NotificationCompat.BigTextStyle()) - if (!app.notifier.shouldBeQuiet()) { - groupBuilder.setDefaults(app.notifier.notificationDefaults) - } - app.notifier.notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build()) - } - - app.db.notificationDao().setAllPosted() - - taskCallback.onCompleted() - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt deleted file mode 100644 index 610ae53e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-25. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData -import pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin.IdziennikFirstLogin -import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLogin -import pl.szczodrzynski.edziennik.api.v2.idziennikLoginMethods -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.prepare -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.utils.Utils.d - -class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { - companion object { - private const val TAG = "Idziennik" - } - - val internalErrorList = mutableListOf() - val data: DataIdziennik - - init { - data = DataIdziennik(app, profile, loginStore).apply { - callback = wrapCallback(this@Idziennik.callback) - satisfyLoginMethods() - } - } - - private fun completed() { - data.saveData() - data.notifyAndSyncEvents { - callback.onCompleted() - } - } - - /* _______ _ _ _ _ _ - |__ __| | /\ | | (_) | | | - | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ - | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ - | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | - |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| - __/ | - |__*/ - override fun sync(featureIds: List, viewId: Int?, arguments: JsonObject?) { - data.arguments = arguments - data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId) - d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") - IdziennikLogin(data) { - IdziennikData(data) { - completed() - } - } - } - - override fun getMessage(message: MessageFull) { - - } - - override fun markAllAnnouncementsAsRead() { - - } - - override fun firstLogin() { - IdziennikFirstLogin(data) { - completed() - } - } - - override fun cancel() { - d(TAG, "Cancelled") - data.cancel() - } - - private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { - return object : EdziennikCallback { - override fun onCompleted() { - callback.onCompleted() - } - - override fun onProgress(step: Float) { - callback.onProgress(step) - } - - override fun onStartProgress(stringRes: Int) { - callback.onStartProgress(stringRes) - } - - override fun onError(apiError: ApiError) { - when (apiError.errorCode) { - in internalErrorList -> { - // finish immediately if the same error occurs twice during the same sync - callback.onError(apiError) - } - CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { - internalErrorList.add(apiError.errorCode) - loginStore.removeLoginData("refreshToken") // force a clean login - //loginLibrus() - } - else -> callback.onError(apiError) - } - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesInbox.kt deleted file mode 100644 index abe3c140..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesInbox.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-30. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api - -import com.google.gson.JsonArray -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_MESSAGES_INBOX -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi -import pl.szczodrzynski.edziennik.asJsonObjectList -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_DELETED -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getBoolean -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.utils.Utils.crc32 -import pl.szczodrzynski.edziennik.utils.models.Date - -class IdziennikApiMessagesInbox(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikApi(data) { - companion object { - private const val TAG = "IdziennikApiMessagesInbox" - } - - init { - apiGet(TAG, IDZIENNIK_API_MESSAGES_INBOX) { json -> - if (json !is JsonArray) { - onSuccess() - return@apiGet - } - - json.asJsonObjectList()?.forEach { jMessage -> - val subject = jMessage.getString("tytul") - if (subject?.contains("(") == true && subject.startsWith("iDziennik - ")) - return@forEach - if (subject?.startsWith("Uwaga dla ucznia (klasa:") == true) - return@forEach - - val messageIdStr = jMessage.getString("id") - val messageId = crc32((messageIdStr + "0").toByteArray()) - - var body = "[META:$messageIdStr;-1]" - body += jMessage.getString("tresc")?.replace("\n".toRegex(), "
") - - val readDate = if (jMessage.getBoolean("odczytana") == true) Date.fromIso(jMessage.getString("wersjaRekordu")) else 0 - val sentDate = Date.fromIso(jMessage.getString("dataWyslania")) - - val sender = jMessage.getAsJsonObject("nadawca") - val rTeacher = data.getTeacher( - sender.getString("imie") ?: "", - sender.getString("nazwisko") ?: "" - ) - rTeacher.loginId = sender.getString("id") + ":" + sender.getString("usr") - - val message = Message( - profileId, - messageId, - subject, - body, - if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED, - rTeacher.id, - -1 - ) - - val messageRecipient = MessageRecipient( - profileId, - -1 /* me */, - -1, - readDate, - /*messageId*/ messageId - ) - - data.messageIgnoreList.add(message) - data.messageRecipientList.add(messageRecipient) - data.messageMetadataList.add(Metadata( - profileId, - Metadata.TYPE_MESSAGE, - message.id, - readDate > 0, - readDate > 0 || profile?.empty ?: false, - sentDate - )) - } - - data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAnnouncements.kt deleted file mode 100644 index 5f700482..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAnnouncements.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.utils.models.Date - -class IdziennikWebAnnouncements(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { - companion object { - private const val TAG = "IdziennikWebAnnouncements" - } - - init { - val param = JsonObject() - param.add("parametryFiltrow", JsonArray()) - - webApiGet(TAG, IDZIENNIK_WEB_ANNOUNCEMENTS, mapOf( - "uczenId" to (data.studentId ?: ""), - "param" to param - )) { result -> - val json = result.getJsonObject("d") ?: run { - data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) - .withApiResponse(result)) - return@webApiGet - } - - for (jAnnouncementEl in json.getAsJsonArray("ListK")) { - val jAnnouncement = jAnnouncementEl.asJsonObject - // jAnnouncement - val announcementId = jAnnouncement.get("Id").asLong - - val rTeacher = data.getTeacherByFirstLast(jAnnouncement.get("Autor").asString) - val addedDate = java.lang.Long.parseLong(jAnnouncement.get("DataDodania").asString.replace("[^\\d]".toRegex(), "")) - val startDate = Date.fromMillis(java.lang.Long.parseLong(jAnnouncement.get("DataWydarzenia").asString.replace("[^\\d]".toRegex(), ""))) - - val announcementObject = Announcement( - profileId, - announcementId, - jAnnouncement.get("Temat").asString, - jAnnouncement.get("Tresc").asString, - startDate, - null, - rTeacher.id - ) - data.announcementList.add(announcementObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_ANNOUNCEMENT, - announcementObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate - )) - } - - data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt deleted file mode 100644 index 1bcbddac..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web - -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_ATTENDANCE -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.crc16 -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.* -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time - -class IdziennikWebAttendance(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { - companion object { - private const val TAG = "IdziennikWebAttendance" - } - - private var attendanceYear = Date.getToday().year - private var attendanceMonth = Date.getToday().month - private var attendancePrevMonthChecked = false - - init { - getAttendance() - } - - private fun getAttendance() { - webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf( - "idPozDziennika" to data.registerId, - "mc" to attendanceMonth, - "rok" to attendanceYear, - "dataTygodnia" to "" - )) { result -> - val json = result.getJsonObject("d") ?: run { - data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) - .withApiResponse(result)) - return@webApiGet - } - - for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) { - val jAttendance = jAttendanceEl.asJsonObject - // jAttendance - val attendanceTypeIdziennik = jAttendance.get("TypObecnosci").asInt - if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7) - continue - val attendanceDate = Date.fromY_m_d(jAttendance.get("Data").asString) - val attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) - if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis()) - continue - - val attendanceId = jAttendance.get("IdLesson").asString.crc16().toLong() - val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "") - val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString) - - var attendanceName = "obecność" - var attendanceType = Attendance.TYPE_CUSTOM - - when (attendanceTypeIdziennik) { - 1 /* nieobecność usprawiedliwiona */ -> { - attendanceName = "nieobecność usprawiedliwiona" - attendanceType = TYPE_ABSENT_EXCUSED - } - 2 /* spóźnienie */ -> { - attendanceName = "spóźnienie" - attendanceType = TYPE_BELATED - } - 3 /* nieobecność nieusprawiedliwiona */ -> { - attendanceName = "nieobecność nieusprawiedliwiona" - attendanceType = TYPE_ABSENT - } - 4 /* zwolnienie */, 9 /* zwolniony / obecny */ -> { - attendanceType = TYPE_RELEASED - if (attendanceTypeIdziennik == 4) - attendanceName = "zwolnienie" - if (attendanceTypeIdziennik == 9) - attendanceName = "zwolnienie / obecność" - } - 0 /* obecny */, 8 /* Wycieczka */ -> { - attendanceType = TYPE_PRESENT - if (attendanceTypeIdziennik == 8) - attendanceName = "wycieczka" - } - } - - val semester = profile?.dateToSemester(attendanceDate) ?: 1 - - val attendanceObject = Attendance( - profileId, - attendanceId, - rTeacher.id, - rSubject.id, - semester, - attendanceName, - attendanceDate, - attendanceTime, - attendanceType - ) - - data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_ATTENDANCE, - attendanceObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() - )) - } - } - - val attendanceDateValue = attendanceYear * 10000 + attendanceMonth * 100 - if (profile?.empty == true && attendanceDateValue > profile?.getSemesterStart(1)?.value ?: 99999999) { - attendancePrevMonthChecked = true // do not need to check prev month later - attendanceMonth-- - if (attendanceMonth < 1) { - attendanceMonth = 12 - attendanceYear-- - } - getAttendance() - } else if (!attendancePrevMonthChecked /* get also the previous month */) { - attendanceMonth-- - if (attendanceMonth < 1) { - attendanceMonth = 12 - attendanceYear-- - } - attendancePrevMonthChecked = true - getAttendance() - } else { - data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) - onSuccess() - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt deleted file mode 100644 index 3a9a3982..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_EXAMS -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.utils.models.Date - -class IdziennikWebExams(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { - companion object { - private const val TAG = "IdziennikWebExams" - } - - private var examsYear = Date.getToday().year - private var examsMonth = Date.getToday().month - private var examsMonthsChecked = 0 - private var examsNextMonthChecked = false // TO DO temporary // no more // idk - - init { - getExams() - } - - private fun getExams() { - val param = JsonObject() - param.addProperty("strona", 1) - param.addProperty("iloscNaStrone", "99") - param.addProperty("iloscRekordow", -1) - param.addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu") - param.addProperty("kierunekSort", 0) - param.addProperty("maxIloscZaznaczonych", 0) - param.addProperty("panelFiltrow", 0) - - webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf( - "idP" to data.registerId, - "rok" to examsYear, - "miesiac" to examsMonth, - "param" to param - )) { result -> - val json = result.getJsonObject("d") ?: run { - data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) - .withApiResponse(result)) - return@webApiGet - } - - for (jExamEl in json.getAsJsonArray("ListK")) { - val jExam = jExamEl.asJsonObject - // jExam - val eventId = jExam.get("_recordId").asLong - val rSubject = data.getSubject(jExam.get("przedmiot").asString, -1, "") - val rTeacher = data.getTeacherByLastFirst(jExam.get("wpisal").asString) - val examDate = Date.fromY_m_d(jExam.get("data").asString) - val lessonObject = Lesson.getByWeekDayAndSubject(data.lessonList, examDate.weekDay, rSubject.id) - val examTime = lessonObject?.startTime - - val eventType = if (jExam.get("rodzaj").asString == "sprawdzian/praca klasowa") Event.TYPE_EXAM else Event.TYPE_SHORT_QUIZ - val eventObject = Event( - profileId, - eventId, - examDate, - examTime, - jExam.get("zakres").asString, - -1, - eventType, - false, - rTeacher.id, - rSubject.id, - data.teamClass?.id ?: -1 - ) - - data.eventList.add(eventObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_EVENT, - eventObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() - )) - } - - if (profile?.empty == true && examsMonthsChecked < 3 /* how many months backwards to check? */) { - examsMonthsChecked++ - examsMonth-- - if (examsMonth < 1) { - examsMonth = 12 - examsYear-- - } - getExams() - } else if (!examsNextMonthChecked /* get also one month forward */) { - val showDate = Date.getToday().stepForward(0, 1, 0) - examsYear = showDate.year - examsMonth = showDate.month - examsNextMonthChecked = true - getExams() - } else { - data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS) - onSuccess() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebProposedGrades.kt deleted file mode 100644 index 376aa8cf..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebProposedGrades.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web - -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_MISSING_GRADES -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_PROPOSED -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_YEAR_PROPOSED -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue - -class IdziennikWebProposedGrades(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { - companion object { - private const val TAG = "IdziennikWebProposedGrades" - } - - init { - webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf( - "idPozDziennika" to data.registerId - )) { result -> - val json = result.getJsonObject("d") ?: run { - data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) - .withApiResponse(result)) - return@webApiGet - } - - val jSubjects = json.getAsJsonArray("Przedmioty") - for (jSubjectEl in jSubjects) { - val jSubject = jSubjectEl.getAsJsonObject() - // jSubject - val rSubject = data.getSubject(jSubject.get("Przedmiot").getAsString(), -1, jSubject.get("Przedmiot").getAsString()) - val semester1Proposed = jSubject.get("OcenaSem1").getAsString() - val semester2Proposed = jSubject.get("OcenaSem2").getAsString() - val semester1Value = getWordGradeValue(semester1Proposed) - val semester2Value = getWordGradeValue(semester2Proposed) - val semester1Id = rSubject.id * -100 - 1 - val semester2Id = rSubject.id * -100 - 2 - - if (semester1Proposed != "") { - val gradeObject = Grade( - profileId, - semester1Id, - "", - -1, - "", - semester1Value.toString(), - semester1Value.toFloat(), - 0f, - 1, - -1, - rSubject.id) - - gradeObject.type = TYPE_SEMESTER1_PROPOSED - - data.gradeList.add(gradeObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_GRADE, - gradeObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() - )) - } - - if (semester2Proposed != "") { - val gradeObject = Grade( - profileId, - semester2Id, - "", - -1, - "", - semester2Value.toString(), - semester2Value.toFloat(), - 0f, - 2, - -1, - rSubject.id) - - gradeObject.type = TYPE_YEAR_PROPOSED - - data.gradeList.add(gradeObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_GRADE, - gradeObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() - )) - } - } - - data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebTimetable.kt deleted file mode 100644 index c9cf7f5f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebTimetable.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-27. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web - -import androidx.core.util.set -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_TIMETABLE -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CANCELLED -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CHANGE -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time -import pl.szczodrzynski.edziennik.utils.models.Week - -class IdziennikWebTimetable(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { - companion object { - private const val TAG = "IdziennikWebTimetable" - } - - init { - val weekStart = Week.getWeekStart() - if (Date.getToday().weekDay > 4) { - weekStart.stepForward(0, 0, 7) - } - - webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf( - "idPozDziennika" to data.registerId, - "pidRokSzkolny" to data.schoolYearId, - "data" to weekStart.stringY_m_d+"T10:00:00.000Z" - )) { result -> - val json = result.getJsonObject("d") ?: run { - data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) - .withApiResponse(result)) - return@webApiGet - } - - json.getJsonArray("GodzinyLekcyjne")?.asJsonObjectList()?.forEach { range -> - val lessonRange = LessonRange( - profileId, - range.getInt("LiczbaP") ?: return@forEach, - range.getString("Poczatek")?.let { Time.fromH_m(it) } ?: return@forEach, - range.getString("Koniec")?.let { Time.fromH_m(it) } ?: return@forEach - ) - data.lessonRanges[lessonRange.lessonNumber] = lessonRange - } - - json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson -> - val subject = data.getSubject( - lesson.getString("Nazwa") ?: return@forEach, - lesson.getLong("Id"), - lesson.getString("Skrot") ?: "" - ) - val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") ?: return@forEach) - val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach - val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) ?: return@forEach] - - val lessonObject = Lesson( - profileId, - weekDay, - lessonRange.startTime, - lessonRange.endTime - ).apply { - subjectId = subject.id - teacherId = teacher.id - teamId = data.teamClass?.id ?: -1 - classroomName = lesson.getString("NazwaSali") ?: "" - } - - data.lessonList.add(lessonObject) - - val type = lesson.getInt("TypZastepstwa") ?: -1 - if (type != -1) { - // we have a lesson change to process - val lessonChangeObject = LessonChange( - profileId, - weekStart.clone().stepForward(0, 0, weekDay), - lessonObject.startTime, - lessonObject.endTime - ) - - lessonChangeObject.teamId = lessonObject.teamId - lessonChangeObject.teacherId = lessonObject.teacherId - lessonChangeObject.subjectId = lessonObject.subjectId - lessonChangeObject.classroomName = lessonObject.classroomName - when (type) { - 0 -> lessonChangeObject.type = TYPE_CANCELLED - 1, 2, 3, 4, 5 -> { - lessonChangeObject.type = TYPE_CHANGE - val newTeacher = lesson.getString("NauZastepujacy") - val newSubject = lesson.getString("PrzedmiotZastepujacy") - if (newTeacher != null) { - lessonChangeObject.teacherId = data.getTeacherByFDotLast(newTeacher).id - } - if (newSubject != null) { - lessonChangeObject.subjectId = data.getSubject(newSubject, null, "").id - } - } - } - - data.lessonChangeList.add(lessonChangeObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_LESSON_CHANGE, - lessonChangeObject.id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() - )) - } - } - - data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt deleted file mode 100644 index b282643b..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-27. - */ - -package pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin - -import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.api.v2.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_SETTINGS -import pl.szczodrzynski.edziennik.api.v2.Regexes -import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.fixName -import pl.szczodrzynski.edziennik.get -import pl.szczodrzynski.edziennik.swapFirstLastName - -class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "IdziennikFirstLogin" - } - - private val web = IdziennikWeb(data) - private val profileList = mutableListOf() - - init { - IdziennikLoginWeb(data) { - web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text -> - //val accounts = json.getJsonArray("accounts") - - val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0" - val accountNameLong = if (isParent) - Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName() - else - null - - var schoolYearName: String? = null - val schoolYear = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { - schoolYearName = it[2] - it[1].toIntOrNull() - } ?: run { - data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR) - .withApiResponse(text)) - return@webGet - } - - Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) - .toMutableList() - .reversed() - .forEach { match -> - val registerId = match[1].toIntOrNull() ?: return@forEach - val studentId = match[2] - val firstName = match[3] - val lastName = match[4] - val className = match[5] + " " + match[6] - - val profile = Profile() - profile.studentNameLong = "$firstName $lastName".fixName() - profile.studentNameShort = "$firstName ${lastName[0]}.".fixName() - profile.accountNameLong = accountNameLong - profile.studentClassName = className - profile.studentSchoolYear = schoolYearName - profile.name = profile.studentNameLong - profile.subname = data.webUsername - profile.empty = true - profile.putStudentData("studentId", studentId) - profile.putStudentData("registerId", registerId) - profile.putStudentData("schoolYearId", schoolYear) - profileList.add(profile) - } - - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) - onSuccess() - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/interfaces/EdziennikInterface.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/interfaces/EdziennikInterface.kt deleted file mode 100644 index 855767f1..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/interfaces/EdziennikInterface.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-29. - */ - -package pl.szczodrzynski.edziennik.api.v2.interfaces - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull - -interface EdziennikInterface { - fun sync(featureIds: List, viewId: Int? = null, arguments: JsonObject? = null) - fun getMessage(message: MessageFull) - fun markAllAnnouncementsAsRead() - fun firstLogin() - fun cancel() -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusData.kt deleted file mode 100644 index 940d48fe..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusData.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-5. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data - -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.librus.* -import pl.szczodrzynski.edziennik.api.v2.librus.data.api.* -import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetList -import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaHomework -import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaInfo -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message -import pl.szczodrzynski.edziennik.utils.Utils - -class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "LibrusEndpoints" - } - - init { - nextEndpoint(onSuccess) - } - - private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { - onSuccess() - return - } - if (data.cancelled) { - onSuccess() - return - } - useEndpoint(data.targetEndpointIds.removeAt(0)) { - data.progress(data.progressStep) - nextEndpoint(onSuccess) - } - } - - private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { - Utils.d(TAG, "Using endpoint $endpointId") - when (endpointId) { - /** - * API - */ - ENDPOINT_LIBRUS_API_ME -> { - data.startProgress(R.string.edziennik_progress_endpoint_student_info) - LibrusApiMe(data, onSuccess) - } - ENDPOINT_LIBRUS_API_SCHOOLS -> { - data.startProgress(R.string.edziennik_progress_endpoint_school_info) - LibrusApiSchools(data, onSuccess) - } - ENDPOINT_LIBRUS_API_CLASSES -> { - data.startProgress(R.string.edziennik_progress_endpoint_classes) - LibrusApiClasses(data, onSuccess) - } - ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES -> { - data.startProgress(R.string.edziennik_progress_endpoint_teams) - LibrusApiVirtualClasses(data, onSuccess) - } - ENDPOINT_LIBRUS_API_UNITS -> { - data.startProgress(R.string.edziennik_progress_endpoint_units) - LibrusApiUnits(data, onSuccess) - } - ENDPOINT_LIBRUS_API_USERS -> { - data.startProgress(R.string.edziennik_progress_endpoint_teachers) - LibrusApiUsers(data, onSuccess) - } - ENDPOINT_LIBRUS_API_SUBJECTS -> { - data.startProgress(R.string.edziennik_progress_endpoint_subjects) - LibrusApiSubjects(data, onSuccess) - } - ENDPOINT_LIBRUS_API_CLASSROOMS -> { - data.startProgress(R.string.edziennik_progress_endpoint_classrooms) - LibrusApiClassrooms(data, onSuccess) - } - // TODO push config - ENDPOINT_LIBRUS_API_TIMETABLES -> { - data.startProgress(R.string.edziennik_progress_endpoint_timetable) - LibrusApiTimetables(data, onSuccess) - } - - ENDPOINT_LIBRUS_API_NORMAL_GRADES -> { - data.startProgress(R.string.edziennik_progress_endpoint_grades) - LibrusApiGrades(data, onSuccess) - } - ENDPOINT_LIBRUS_API_NORMAL_GC -> { - data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) - LibrusApiGradeCategories(data, onSuccess) - } - // TODO grades - - ENDPOINT_LIBRUS_API_EVENT_TYPES -> { - data.startProgress(R.string.edziennik_progress_endpoint_event_types) - LibrusApiEventTypes(data, onSuccess) - } - ENDPOINT_LIBRUS_API_EVENTS -> { - data.startProgress(R.string.edziennik_progress_endpoint_events) - LibrusApiEvents(data, onSuccess) - } - ENDPOINT_LIBRUS_API_HOMEWORK -> { - data.startProgress(R.string.edziennik_progress_endpoint_homework) - LibrusApiHomework(data, onSuccess) - } - ENDPOINT_LIBRUS_API_LUCKY_NUMBER -> { - data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) - LibrusApiLuckyNumber(data, onSuccess) - } - ENDPOINT_LIBRUS_API_NOTICE_TYPES -> { - data.startProgress(R.string.edziennik_progress_endpoint_notice_types) - LibrusApiNoticeTypes(data, onSuccess) - } - ENDPOINT_LIBRUS_API_NOTICES -> { - data.startProgress(R.string.edziennik_progress_endpoint_notices) - LibrusApiNotices(data, onSuccess) - } - ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES -> { - data.startProgress(R.string.edziennik_progress_endpoint_attendance_types) - LibrusApiAttendanceTypes(data, onSuccess) - } - ENDPOINT_LIBRUS_API_ATTENDANCES -> { - data.startProgress(R.string.edziennik_progress_endpoint_attendance) - LibrusApiAttendances(data, onSuccess) - } - ENDPOINT_LIBRUS_API_ANNOUNCEMENTS -> { - data.startProgress(R.string.edziennik_progress_endpoint_announcements) - LibrusApiAnnouncements(data, onSuccess) - } - ENDPOINT_LIBRUS_API_PT_MEETINGS -> { - data.startProgress(R.string.edziennik_progress_endpoint_pt_meetings) - LibrusApiPtMeetings(data, onSuccess) - } - ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES -> { - data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_day_types) - LibrusApiTeacherFreeDayTypes(data, onSuccess) - } - ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS -> { - data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_days) - LibrusApiTeacherFreeDays(data, onSuccess) - } - - /** - * SYNERGIA - */ - ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK -> { - data.startProgress(R.string.edziennik_progress_endpoint_homework) - LibrusSynergiaHomework(data, onSuccess) - } - ENDPOINT_LIBRUS_SYNERGIA_INFO -> { - data.startProgress(R.string.edziennik_progress_endpoint_student_info) - LibrusSynergiaInfo(data, onSuccess) - } - - /** - * MESSAGES - */ - ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> { - data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) - LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, onSuccess = onSuccess) - } - ENDPOINT_LIBRUS_MESSAGES_SENT -> { - data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) - LibrusMessagesGetList(data, type = Message.TYPE_SENT, onSuccess = onSuccess) - } - - else -> onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusMessages.kt deleted file mode 100644 index 7de22502..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusMessages.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-24 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data - -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import im.wangchao.mhttp.body.MediaTypeUtils -import im.wangchao.mhttp.callback.TextCallbackHandler -import okhttp3.Cookie -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.parser.Parser -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.utils.Utils.d -import java.io.StringWriter -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.transform.OutputKeys -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult - -open class LibrusMessages(open val data: DataLibrus) { - companion object { - private const val TAG = "LibrusMessages" - } - - val profileId - get() = data.profile?.id ?: -1 - - val profile - get() = data.profile - - fun messagesGet(tag: String, endpoint: String, method: Int = POST, - parameters: Map? = null, onSuccess: (doc: Document) -> Unit) { - - d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$endpoint") - - val callback = object : TextCallbackHandler() { - override fun onSuccess(text: String?, response: Response?) { - if (text.isNullOrEmpty()) { - data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) - .withResponse(response)) - return - } - - when { - text.contains("Niepoprawny login i/lub hasło.") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) - text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) - text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) - text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) - text.contains("error") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) - text.contains("eVarWhitThisNameNotExists") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) - text.contains("") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) - } - - try { - val doc = Jsoup.parse(text, "", Parser.xmlParser()) - onSuccess(doc) - } catch (e: Exception) { - data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) - .withResponse(response) - .withThrowable(e) - .withApiResponse(text)) - } - } - - override fun onFailure(response: Response?, throwable: Throwable?) { - data.error(ApiError(tag, ERROR_REQUEST_FAILURE) - .withResponse(response) - .withThrowable(throwable)) - } - } - - data.app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("DZIENNIKSID") - .value(data.messagesSessionId!!) - .domain("wiadomosci.librus.pl") - .secure().httpOnly().build() - )) - - - val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() - val doc = docBuilder.newDocument() - val serviceElement = doc.createElement("service") - val headerElement = doc.createElement("header") - val dataElement = doc.createElement("data") - for ((key, value) in parameters.orEmpty()) { - val element = doc.createElement(key) - element.appendChild(doc.createTextNode(value.toString())) - dataElement.appendChild(element) - } - serviceElement.appendChild(headerElement) - serviceElement.appendChild(dataElement) - doc.appendChild(serviceElement) - val transformer = TransformerFactory.newInstance().newTransformer() - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") - val stringWriter = StringWriter() - transformer.transform(DOMSource(doc), StreamResult(stringWriter)) - val requestXml = stringWriter.toString() - - /*val requestXml = xml("service") { - "header" { } - "data" { - for ((key, value) in parameters.orEmpty()) { - key { - -value.toString() - } - } - } - }.toString(PrintOptions( - singleLineTextElements = true, - useSelfClosingTags = true - ))*/ - - Request.builder() - .url("$LIBRUS_MESSAGES_URL/$endpoint") - .userAgent(SYNERGIA_USER_AGENT) - .setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML) - .apply { - when (method) { - GET -> get() - POST -> post() - } - } - .callback(callback) - .build() - .enqueue() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAnnouncements.kt deleted file mode 100644 index e05f1ef0..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAnnouncements.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-13 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.models.Date - -class LibrusApiAnnouncements(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiAnnouncements" - } - - init { - apiGet(TAG, "SchoolNotices") { json -> - val announcements = json.getJsonArray("SchoolNotices").asJsonObjectList() - - announcements?.forEach { announcement -> - val id = Utils.crc16(announcement.getString("Id")?.toByteArray() - ?: return@forEach).toLong() - val subject = announcement.getString("Subject") ?: "" - val text = announcement.getString("Content") ?: "" - val startDate = Date.fromY_m_d(announcement.getString("StartDate")) - val endDate = Date.fromY_m_d(announcement.getString("EndDate")) - val teacherId = announcement.getJsonObject("AddedBy")?.getLong("Id") ?: -1 - val addedDate = Date.fromIso(announcement.getString("CreationDate")) - val read = announcement.getBoolean("WasRead") ?: false - - val announcementObject = Announcement( - profileId, - id, - subject, - text, - startDate, - endDate, - teacherId - ) - - data.announcementList.add(announcementObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_ANNOUNCEMENT, - id, - read, - read, - addedDate - )) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendanceTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendanceTypes.kt deleted file mode 100644 index 1ee34f9f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendanceTypes.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-13 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import android.graphics.Color -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.attendance.AttendanceType - -class LibrusApiAttendanceTypes(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiAttendanceTypes" - } - - init { - apiGet(TAG, "Attendances/Types") { json -> - val attendanceTypes = json.getJsonArray("Types").asJsonObjectList() - - attendanceTypes?.forEach { attendanceType -> - val id = attendanceType.getLong("Id") ?: return@forEach - val name = attendanceType.getString("Name") ?: "" - val color = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } ?: -1 - - val standardId = when (attendanceType.getBoolean("Standard") ?: false) { - true -> id - false -> attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id - } - val type = when (standardId) { - 1L -> Attendance.TYPE_ABSENT - 2L -> Attendance.TYPE_BELATED - 3L -> Attendance.TYPE_ABSENT_EXCUSED - 4L -> Attendance.TYPE_RELEASED - /*100*/else -> Attendance.TYPE_PRESENT - } - - data.attendanceTypes.put(id, AttendanceType(profileId, id, name, type, color)) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 4*DAY) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendances.kt deleted file mode 100644 index 52a3ea26..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiAttendances.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-13 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import androidx.core.util.isEmpty -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ATTENDANCES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.models.Date - -class LibrusApiAttendances(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiAttendances" - } - - init { - if (data.attendanceTypes.isEmpty()) { - data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } - } - - apiGet(TAG, "Attendances") { json -> - val attendances = json.getJsonArray("Attendances").asJsonObjectList() - - attendances?.forEach { attendance -> - val id = Utils.strToInt((attendance.getString("Id") ?: return@forEach) - .replace("[^\\d.]".toRegex(), "")).toLong() - val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") ?: -1 - val lessonNo = attendance.getInt("LessonNo") ?: return@forEach - val startTime = data.lessonRanges.get(lessonNo).startTime - val lessonDate = Date.fromY_m_d(attendance.getString("Date")) - val subjectId = data.lessonList.singleOrNull { - it.weekDay == lessonDate.weekDay && it.startTime.value == startTime.value - }?.subjectId ?: -1 - val semester = attendance.getInt("Semester") ?: return@forEach - val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach - val typeObject = data.attendanceTypes.get(type) - val topic = typeObject?.name ?: "" - - val attendanceObject = Attendance( - profileId, - id, - teacherId, - subjectId, - semester, - topic, - lessonDate, - startTime, - typeObject.type - ) - - val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) - - data.attendanceList.add(attendanceObject) - if(typeObject.type != Attendance.TYPE_PRESENT) { - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_ATTENDANCE, - id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate - )) - } - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCES, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEvents.kt deleted file mode 100644 index 512a8caa..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEvents.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-4. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import androidx.core.util.isEmpty -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENTS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time - -class LibrusApiEvents(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiEvents" - } - - init { - if (data.eventTypes.isEmpty()) { - data.db.eventTypeDao().getAllNow(profileId).toSparseArray(data.eventTypes) { it.id } - } - - apiGet(TAG, "HomeWorks") { json -> - val events = json.getJsonArray("HomeWorks").asJsonObjectList() - - events?.forEach { event -> - val id = event.getLong("Id") ?: return@forEach - val eventDate = Date.fromY_m_d(event.getString("Date")) - val topic = event.getString("Content") ?: "" - val type = event.getJsonObject("Category")?.getInt("Id") ?: -1 - val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1 - val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1 - val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1 - - val lessonNo = event.getInt("LessonNo") - val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNo } - val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom")) - val addedDate = Date.fromIso(event.getString("AddDate")) - - val eventObject = Event( - profileId, - id, - eventDate, - startTime, - topic, - -1, - type, - false, - teacherId, - subjectId, - teamId - ) - - data.eventList.add(eventObject) - data.metadataList.add( - Metadata( - profileId, - Metadata.TYPE_EVENT, - id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate - )) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGrades.kt deleted file mode 100644 index 930938d0..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGrades.kt +++ /dev/null @@ -1,97 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.models.Date - -class LibrusApiGrades(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiGrades" - } - - init { - apiGet(TAG, "Grades") { json -> - val grades = json.getJsonArray("Grades").asJsonObjectList() - - grades?.forEach { grade -> - val id = grade.getLong("Id") ?: return@forEach - val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1 - val name = grade.getString("Grade") ?: "" - val semester = grade.getInt("Semester") ?: return@forEach - val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1 - val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: -1 - val addedDate = Date.fromIso(grade.getString("AddDate")) - - val category = data.gradeCategories.singleOrNull { it.categoryId == categoryId } - val categoryName = category?.text ?: "" - val color = category?.color ?: -1 - var weight = category?.weight ?: 0f - val value = Utils.getGradeValue(name) - - - if (name == "-" || name == "+" - || name.equals("np", ignoreCase = true) - || name.equals("bz", ignoreCase = true)) { - weight = 0f - } - - val gradeObject = Grade( - profileId, - id, - categoryName, - color, - "", - name, - value, - weight, - semester, - teacherId, - subjectId - ) - - when { - grade.getBoolean("IsConstituent") ?: false -> - gradeObject.type = Grade.TYPE_NORMAL - grade.getBoolean("IsSemester") ?: false -> // semester final - gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER2_FINAL - grade.getBoolean("IsSemesterProposition") ?: false -> // semester proposed - gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_PROPOSED else Grade.TYPE_SEMESTER2_PROPOSED - grade.getBoolean("IsFinal") ?: false -> // year final - gradeObject.type = Grade.TYPE_YEAR_FINAL - grade.getBoolean("IsFinalProposition") ?: false -> // year final - gradeObject.type = Grade.TYPE_YEAR_PROPOSED - } - - grade.getJsonObject("Improvement")?.also { - val historicalId = it.getLong("Id") - data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade -> - grade.parentId = gradeObject.id - if (grade.name == "nb") grade.weight = 0f - } - gradeObject.isImprovement = true - } - - data.gradeList.add(gradeObject) - data.metadataList.add( - Metadata( - profileId, - Metadata.TYPE_GRADE, - id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate - )) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADES, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiHomework.kt deleted file mode 100644 index ba16ebe9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiHomework.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-12. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_HOMEWORK -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.models.Date - -class LibrusApiHomework(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiHomework" - } - - init { - apiGet(TAG, "HomeWorkAssignments") { json -> - val homeworkList = json.getJsonArray("HomeWorkAssignments").asJsonObjectList() - - homeworkList?.forEach { homework -> - val id = homework.getLong("Id") ?: return@forEach - val eventDate = Date.fromY_m_d(homework.getString("DueDate")) - val topic = homework.getString("Topic") + "\n" + homework.getString("Text") - val teacherId = homework.getJsonObject("Teacher")?.getLong("Id") ?: -1 - val addedDate = Date.fromY_m_d(homework.getString("Date")) - - val eventObject = Event( - profileId, - id, - eventDate, - null, - topic, - -1, - -1, - false, - teacherId, - -1, - -1 - ) - - data.eventList.add(eventObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_HOMEWORK, - id, - profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis - )) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNoticeTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNoticeTypes.kt deleted file mode 100644 index f49b15c7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNoticeTypes.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-24. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NOTICE_TYPES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeType - -class LibrusApiNoticeTypes(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiNoticeTypes" - } - - init { - apiGet(TAG, "Notes/Categories") { json -> - val noticeTypes = json.getJsonArray("Categories").asJsonObjectList() - - noticeTypes?.forEach { noticeType -> - val id = noticeType.getLong("Id") ?: return@forEach - val name = noticeType.getString("CategoryName") ?: "" - - data.noticeTypes.put(id, NoticeType(profileId, id, name)) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICE_TYPES, 4*DAY) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTemplate.kt deleted file mode 100644 index efa7baf6..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTemplate.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-4. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS - -class LibrusApiTemplate(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApi" - } - - init { - /*apiGet(TAG, "") { json -> - - data.setSyncNext(ENDPOINT_LIBRUS_API_, SYNC_ALWAYS) - onSuccess() - }*/ - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUsers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUsers.kt deleted file mode 100644 index 9b1b3c93..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUsers.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-23. - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_USERS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher - -class LibrusApiUsers(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { - companion object { - const val TAG = "LibrusApiUsers" - } - - init { - apiGet(TAG, "Users") { json -> - val users = json.getJsonArray("Users").asJsonObjectList() - - users?.forEach { user -> - val id = user.getLong("Id") ?: return@forEach - val firstName = user.getString("FirstName")?.fixName() ?: "" - val lastName = user.getString("LastName")?.fixName() ?: "" - - data.teacherList.put(id, Teacher(profileId, id, firstName, lastName)) - } - - data.setSyncNext(ENDPOINT_LIBRUS_API_USERS, 4*DAY) - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesTemplate.kt deleted file mode 100644 index f2a84d68..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesTemplate.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-25 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.messages - -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS - -class LibrusMessagesTemplate(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusMessages(data) { - companion object { - const val TAG = "LibrusMessages" - } - - init { - /* messagesGet(TAG, "") { doc -> - - data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_, SYNC_ALWAYS) - onSuccess() - } */ - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt deleted file mode 100644 index 5976954a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-26 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia - -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia - -class LibrusSynergiaMarkAllAnnouncementsAsRead(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) { - companion object { - const val TAG = "LibrusSynergiaMarkAllAnnouncementsAsRead" - } - - init { - synergiaGet(TAG, "ogloszenia") { - onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaTemplate.kt deleted file mode 100644 index 1cc0097e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaTemplate.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-23 - */ - -package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia - -import org.jsoup.Jsoup -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS - -class LibrusSynergiaTemplate(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) { - companion object { - const val TAG = "LibrusSynergia" - } - - init { - /* synergiaGet(TAG, "") { text -> - val doc = Jsoup.parse(text) - - data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_, SYNC_ALWAYS) - onSuccess() - } */ - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt deleted file mode 100644 index 41d77921..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt +++ /dev/null @@ -1,92 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin - -import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT -import pl.szczodrzynski.edziennik.api.v2.FAKE_LIBRUS_ACCOUNTS -import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNTS_URL -import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL -import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusPortal -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.api.AppError.CODE_LIBRUS_DISCONNECTED -import pl.szczodrzynski.edziennik.data.api.AppError.CODE_SYNERGIA_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile - -class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "LibrusFirstLogin" - } - - private val portal = LibrusPortal(data) - private val api = LibrusApi(data) - private val profileList = mutableListOf() - - init { - if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) { - // email login: use Portal for account list - LibrusLoginPortal(data) { - portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response -> - val accounts = json.getJsonArray("accounts") - - if (accounts == null || accounts.size() < 1) { - data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT) - .withResponse(response) - .withApiResponse(json)) - return@portalGet - } - val accountDataTime = json.getLong("lastModification") - - for (accountEl in accounts) { - val account = accountEl.asJsonObject - - val state = account.getString("state") - when (state) { - "requiring_an_action" -> CODE_LIBRUS_DISCONNECTED - "need-activation" -> CODE_SYNERGIA_NOT_ACTIVATED - else -> null - }?.let { errorCode -> - data.error(ApiError(TAG, errorCode) - .withApiResponse(json) - .withResponse(response)) - return@portalGet - } - - val id = account.getInt("id") ?: continue - val login = account.getString("login") ?: continue - val token = account.getString("accessToken") ?: continue - val tokenTime = (accountDataTime ?: 0) + DAY - val name = account.getString("studentName")?.fixName() ?: "" - - val profile = Profile() - profile.studentNameLong = name - profile.studentNameShort = name.getShortName() - profile.name = profile.studentNameLong - profile.subname = data.portalEmail - profile.empty = true - profile.putStudentData("accountId", id) - profile.putStudentData("accountLogin", login) - profile.putStudentData("accountToken", token) - profile.putStudentData("accountTokenTime", tokenTime) - profileList.add(profile) - } - - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) - onSuccess() - } - } - } - else { - // synergia or JST login: use Api for account info - LibrusLoginApi(data) { - api.apiGet(TAG, "Me") { json -> - - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/Mobidziennik.kt deleted file mode 100644 index b4c825f1..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/Mobidziennik.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-5. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin -import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.prepare -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.utils.Utils.d - -class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { - companion object { - private const val TAG = "Mobidziennik" - } - - val internalErrorList = mutableListOf() - val data: DataMobidziennik - - init { - data = DataMobidziennik(app, profile, loginStore).apply { - callback = wrapCallback(this@Mobidziennik.callback) - satisfyLoginMethods() - } - } - - private fun completed() { - data.saveData() - data.notifyAndSyncEvents { - callback.onCompleted() - } - } - - /* _______ _ _ _ _ _ - |__ __| | /\ | | (_) | | | - | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ - | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ - | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | - |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| - __/ | - |__*/ - override fun sync(featureIds: List, viewId: Int?, arguments: JsonObject?) { - data.arguments = arguments - data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId) - d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") - MobidziennikLogin(data) { - MobidziennikData(data) { - completed() - } - } - } - - override fun getMessage(message: MessageFull) { - - } - - override fun markAllAnnouncementsAsRead() { - - } - - override fun firstLogin() { - MobidziennikFirstLogin(data) { - completed() - } - } - - override fun cancel() { - d(TAG, "Cancelled") - data.cancel() - } - - private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { - return object : EdziennikCallback { - override fun onCompleted() { - callback.onCompleted() - } - - override fun onProgress(step: Float) { - callback.onProgress(step) - } - - override fun onStartProgress(stringRes: Int) { - callback.onStartProgress(stringRes) - } - - override fun onError(apiError: ApiError) { - when (apiError.errorCode) { - in internalErrorList -> { - // finish immediately if the same error occurs twice during the same sync - callback.onError(apiError) - } - CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { - internalErrorList.add(apiError.errorCode) - loginStore.removeLoginData("refreshToken") // force a clean login - //loginLibrus() - } - else -> callback.onError(apiError) - } - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikData.kt deleted file mode 100644 index e6f2aca5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikData.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-5. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data - -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.* -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api.MobidziennikApi -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebCalendar -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGrades -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebMessagesAll -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebMessagesInbox -import pl.szczodrzynski.edziennik.utils.Utils - -class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "MobidziennikData" - } - - init { - nextEndpoint(onSuccess) - } - - private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { - onSuccess() - return - } - if (data.cancelled) { - onSuccess() - return - } - useEndpoint(data.targetEndpointIds.removeAt(0)) { - data.progress(data.progressStep) - nextEndpoint(onSuccess) - } - } - - private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { - Utils.d(TAG, "Using endpoint $endpointId") - when (endpointId) { - ENDPOINT_MOBIDZIENNIK_API_MAIN -> { - data.startProgress(R.string.edziennik_progress_endpoint_data) - MobidziennikApi(data, onSuccess) - } - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX -> { - data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) - MobidziennikWebMessagesInbox(data) { onSuccess() } - } - ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL -> { - data.startProgress(R.string.edziennik_progress_endpoint_messages) - MobidziennikWebMessagesAll(data) { onSuccess() } - } - ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR -> { - data.startProgress(R.string.edziennik_progress_endpoint_calendar) - MobidziennikWebCalendar(data) { onSuccess() } - } - ENDPOINT_MOBIDZIENNIK_WEB_GRADES -> { - data.startProgress(R.string.edziennik_progress_endpoint_grades) - MobidziennikWebGrades(data) { onSuccess() } - }/* - ENDPOINT_MOBIDZIENNIK_WEB_NOTICES -> { - data.startProgress(R.string.edziennik_progress_endpoint_behaviour) - MobidziennikWebNotices(data) { onSuccess() } - } - ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE -> { - data.startProgress(R.string.edziennik_progress_endpoint_attendance) - MobidziennikWebAttendance(data) { onSuccess() } - } - ENDPOINT_MOBIDZIENNIK_WEB_MANUALS -> { - data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) - MobidziennikWebManuals(data) { onSuccess() } - }*/ - else -> onSuccess() - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikWeb.kt deleted file mode 100644 index 67828b73..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/MobidziennikWeb.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-5. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data - -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import im.wangchao.mhttp.callback.TextCallbackHandler -import okhttp3.Cookie -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.utils.Utils.d - -open class MobidziennikWeb(open val data: DataMobidziennik) { - companion object { - private const val TAG = "MobidziennikWeb" - } - - val profileId - get() = data.profile?.id ?: -1 - - val profile - get() = data.profile - - fun webGet(tag: String, endpoint: String, method: Int = GET, payload: List>? = null, onSuccess: (text: String) -> Unit) { - val url = "https://${data.loginServerName}.mobidziennik.pl$endpoint" - - d(tag, "Request: Mobidziennik/Web - $url") - - if (data.webSessionKey == null) { - data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY) - return - } - if (data.webSessionValue == null) { - data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE) - return - } - if (data.webServerId == null) { - data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID) - return - } - - val callback = object : TextCallbackHandler() { - override fun onSuccess(text: String?, response: Response?) { - if (text.isNullOrEmpty()) { - data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) - .withResponse(response)) - return - } - if (text == "Nie jestes zalogowany" - || text.contains("przypomnij_haslo_email")) { - data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED) - .withResponse(response)) - return - } - - try { - onSuccess(text) - } catch (e: Exception) { - data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_REQUEST) - .withResponse(response) - .withThrowable(e) - .withApiResponse(text)) - } - } - - override fun onFailure(response: Response?, throwable: Throwable?) { - data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) - .withResponse(response) - .withThrowable(throwable)) - } - } - - data.app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name(data.webSessionKey!!) - .value(data.webSessionValue!!) - .domain("${data.loginServerName}.mobidziennik.pl") - .secure().httpOnly().build(), - Cookie.Builder() - .name("SERVERID") - .value(data.webServerId!!) - .domain("${data.loginServerName}.mobidziennik.pl") - .secure().httpOnly().build() - )) - - Request.builder() - .url(url) - .userAgent(MOBIDZIENNIK_USER_AGENT) - .callback(callback) - .build() - .enqueue() - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiAttendance.kt deleted file mode 100644 index a22093e7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiAttendance.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-11. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api - -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.* -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata - -class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { - init { run { - for (row in rows) { - if (row.isEmpty()) - continue - val cols = row.split("|") - - val studentId = cols[2].toInt() - if (studentId != data.studentId) - return@run - - val id = cols[0].toLong() - val lessonId = cols[1].toLong() - data.mobiLessons.singleOrNull { it.id == lessonId }?.let { lesson -> - val type = when (cols[4]) { - "2" -> TYPE_ABSENT - "5" -> TYPE_ABSENT_EXCUSED - "4" -> TYPE_RELEASED - else -> TYPE_PRESENT - } - val semester = data.profile?.dateToSemester(lesson.date) ?: 1 - - val attendanceObject = Attendance( - data.profileId, - id, - lesson.teacherId, - lesson.subjectId, - semester, - lesson.topic, - lesson.date, - lesson.startTime, - type) - - data.attendanceList.add(attendanceObject) - data.metadataList.add( - Metadata( - data.profileId, - Metadata.TYPE_ATTENDANCE, - id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() - )) - } - } - }} -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt deleted file mode 100644 index e022bdf8..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-11. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api - -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson -import pl.szczodrzynski.edziennik.fixName -import pl.szczodrzynski.edziennik.singleOrNull -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time - -class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { - init { - val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") } - - val dataStart = Date.getToday() - val dataEnd = dataStart.clone().stepForward(0, 0, 7 + (6 - dataStart.weekDay)) - - data.toRemove.add(DataRemoveModel.Timetable.between(dataStart.clone(), dataEnd)) - - val dataDays = mutableListOf() - while (dataStart <= dataEnd) { - dataDays += dataStart.value - dataStart.stepForward(0, 0, 1) - } - - for (lesson in lessons) { - val date = Date.fromYmd(lesson[2]) - val startTime = Time.fromYmdHm(lesson[3]) - val endTime = Time.fromYmdHm(lesson[4]) - val id = date.combineWith(startTime) / 6L * 10L + (lesson.joinToString("|").hashCode() and 0xFFFF) - - dataDays.remove(date.value) - - val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5] }?.id ?: -1 - val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.id ?: -1 - val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1 - val classroom = lesson[11] - - Lesson(data.profileId, id).also { - when (lesson[1]) { - "plan_lekcji", "lekcja" -> { - it.type = Lesson.TYPE_NORMAL - it.date = date - it.startTime = startTime - it.endTime = endTime - it.subjectId = subjectId - it.teacherId = teacherId - it.teamId = teamId - it.classroom = classroom - } - "lekcja_odwolana" -> { - it.type = Lesson.TYPE_CANCELLED - it.date = date - it.startTime = startTime - it.endTime = endTime - it.oldSubjectId = subjectId - //it.oldTeacherId = teacherId - it.oldTeamId = teamId - //it.oldClassroom = classroom - } - "zastepstwo" -> { - it.type = Lesson.TYPE_CHANGE - it.date = date - it.startTime = startTime - it.endTime = endTime - it.subjectId = subjectId - it.teacherId = teacherId - it.teamId = teamId - it.classroom = classroom - } - } - - if (it.type != Lesson.TYPE_NORMAL) { - data.metadataList.add( - Metadata( - data.profileId, - Metadata.TYPE_LESSON_CHANGE, - it.id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() - )) - } - data.lessonNewList += it - } - } - - for (day in dataDays) { - val lessonDate = Date.fromValue(day) - data.lessonNewList += Lesson(data.profileId, lessonDate.value.toLong()).apply { - type = Lesson.TYPE_NO_LESSONS - date = lessonDate - } - } - - /*for (lessonStr in rows) { - if (lessonStr.isNotEmpty()) { - val lesson = lessonStr.split("|") - - if (lesson[0].toInt() != data.studentId) - continue - - if (lesson[1] == "plan_lekcji" || lesson[1] == "lekcja") { - val lessonObject = Lesson(data.profileId, lesson[2], lesson[3], lesson[4]) - - data.subjectList.singleOrNull { it.longName == lesson[5] }?.let { - lessonObject.subjectId = it.id - } - data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.let { - lessonObject.teacherId = it.id - } - data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.let { - lessonObject.teamId = it.id - } - lessonObject.classroomName = lesson[11] - data.lessonList.add(lessonObject) - } - } - } - - // searching for all changes - for (lessonStr in rows) { - if (lessonStr.isNotEmpty()) { - val lesson = lessonStr.split("|") - - if (lesson[0].toInt() != data.studentId) - continue - - if (lesson[1] == "zastepstwo" || lesson[1] == "lekcja_odwolana") { - val lessonChange = LessonChange(data.profileId, lesson[2], lesson[3], lesson[4]) - - data.subjectList.singleOrNull { it.longName == lesson[5] }?.let { - lessonChange.subjectId = it.id - } - data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.let { - lessonChange.teacherId = it.id - } - data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.let { - lessonChange.teamId = it.id - } - - if (lesson[1] == "zastepstwo") { - lessonChange.type = LessonChange.TYPE_CHANGE - } - else if (lesson[1] == "lekcja_odwolana") { - lessonChange.type = LessonChange.TYPE_CANCELLED - } - else if (lesson[1] == "lekcja") { - lessonChange.type = LessonChange.TYPE_ADDED - } - lessonChange.classroomName = lesson[11] - - val originalLesson = lessonChange.getOriginalLesson(data.lessonList) - - if (lessonChange.type == LessonChange.TYPE_ADDED) { - if (originalLesson == null) { - // original lesson doesn't exist, save a new addition - // TODO - *//*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) { - app.profile.timetable.addLessonAddition(registerLessonChange); - }*//* - } else { - // original lesson exists, so we need to compare them - if (!lessonChange.matches(originalLesson)) { - // the lessons are different, so it's probably a lesson change - // ahhh this damn API - lessonChange.type = LessonChange.TYPE_CHANGE - } - } - - } - if (lessonChange.type != LessonChange.TYPE_ADDED) { - // it's not a lesson addition - data.lessonChangeList.add(lessonChange) - data.metadataList.add( - Metadata( - data.profileId, - Metadata.TYPE_LESSON_CHANGE, - lessonChange.id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() - )) - if (originalLesson == null) { - // there is no original lesson, so we have to add one in order to change it - data.lessonList.add(Lesson.fromLessonChange(lessonChange)) - } - } - } - } - }*/ - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/web/MobidziennikWebNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/web/MobidziennikWebNotices.kt deleted file mode 100644 index ab7d8760..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/web/MobidziennikWebNotices.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-11. - */ - -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web - -import com.google.gson.JsonParser -import pl.szczodrzynski.edziennik.api.v2.Regexes -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_NOTICES -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.models.Date -import java.util.* - -class MobidziennikWebNotices(override val data: DataMobidziennik, - val onSuccess: () -> Unit) : MobidziennikWeb(data) { - companion object { - private const val TAG = "MobidziennikWebNotices" - } - - init { - // TODO this does no longer work: Mobidziennik changed their mobile page in 2019.09 - data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_NOTICES, SYNC_ALWAYS) - onSuccess() - /*webGet(TAG, "/mobile/zachowanie") { text -> - MobidziennikLuckyNumberExtractor(data, text) - - data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_NOTICES, SYNC_ALWAYS) - onSuccess() - }*/ - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt deleted file mode 100644 index 3742e34b..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt +++ /dev/null @@ -1,60 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin - -import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.fixName -import pl.szczodrzynski.edziennik.utils.models.Date - -class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "MobidziennikFirstLogin" - } - - private val web = MobidziennikWeb(data) - private val profileList = mutableListOf() - - init { - MobidziennikLoginWeb(data) { - web.webGet(TAG, "/api/zrzutbazy") { text -> - val tables = text.split("T@B#LA") - - val accountNameLong = run { - tables[0] - .split("\n") - .map { it.split("|") } - .singleOrNull { it.getOrNull(1) != "*" } - ?.let { - "${it[4]} ${it[5]}".fixName() - } - } - - tables[8].split("\n").forEach { student -> - if (student.isEmpty()) - return@forEach - val student1 = student.split("|") - if (student1.size == 2) - return@forEach - - val today = Date.getToday() - val profile = Profile() - profile.studentNameLong = "${student1[2]} ${student1[4]}".fixName() - profile.studentNameShort = "${student1[2]} ${student1[4][0]}.".fixName() - profile.accountNameLong = if (accountNameLong == profile.studentNameLong) null else accountNameLong - profile.studentSchoolYear = "${today.year}/${today.year+1}" - profile.name = profile.studentNameLong - profile.subname = data.loginUsername - profile.empty = true - profile.putStudentData("studentId", student1[0].toInt()) - profileList.add(profile) - } - - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) - onSuccess() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/ApiError.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/ApiError.kt deleted file mode 100644 index effe2a72..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/ApiError.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-9-28. - */ - -package pl.szczodrzynski.edziennik.api.v2.models - -import android.content.Context -import com.google.gson.JsonObject -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import pl.szczodrzynski.edziennik.data.api.AppError - -class ApiError(val tag: String, val errorCode: Int) { - var profileId: Int? = null - var throwable: Throwable? = null - var apiResponse: String? = null - var request: Request? = null - var response: Response? = null - var isCritical = true - - fun withThrowable(throwable: Throwable?): ApiError { - this.throwable = throwable - return this - } - fun withApiResponse(apiResponse: String?): ApiError { - this.apiResponse = apiResponse - return this - } - fun withApiResponse(apiResponse: JsonObject?): ApiError { - this.apiResponse = apiResponse?.toString() - return this - } - fun withRequest(request: Request?): ApiError { - this.request = request - return this - } - fun withResponse(response: Response?): ApiError { - this.response = response - this.request = response?.request() - return this - } - - fun setCritical(isCritical: Boolean): ApiError { - this.isCritical = isCritical - return this - } - - fun toAppError(): AppError { - return AppError( - tag, - -1, - errorCode, response, throwable, apiResponse - ) - } - - fun getStringReason(context: Context): String { - return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let { - if (it != 0) - context.getString(it) - else - "?" - } - } - - override fun toString(): String { - return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)" - } - - -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt deleted file mode 100644 index c23b28d9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-2. - */ - -package pl.szczodrzynski.edziennik.api.v2.models - -import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeDao -import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao -import pl.szczodrzynski.edziennik.utils.models.Date - -open class DataRemoveModel { - class Timetable(private val dateFrom: Date?, private val dateTo: Date?) : DataRemoveModel() { - companion object { - fun from(dateFrom: Date) = Timetable(dateFrom, null) - fun to(dateTo: Date) = Timetable(null, dateTo) - fun between(dateFrom: Date, dateTo: Date) = Timetable(dateFrom, dateTo) - } - fun commit(profileId: Int, dao: TimetableDao) { - if (dateFrom != null && dateTo != null) { - dao.clearBetweenDates(profileId, dateFrom, dateTo) - } - else { - dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) } - dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) } - } - } - } - class Grades(val all: Boolean, val semester: Int?) : DataRemoveModel() { - companion object { - fun all() = Grades(true, null) - fun semester(semester: Int) = Grades(false, semester) - } - fun commit(profileId: Int, dao: GradeDao) { - if (all) { - dao.clear(profileId) - } - semester?.let { dao.clearForSemester(profileId, it) } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt deleted file mode 100644 index 494ea1d9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-5. - */ - -package pl.szczodrzynski.edziennik.api.v2.template.data - -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.template.DataTemplate -import pl.szczodrzynski.edziennik.api.v2.template.ENDPOINT_TEMPLATE_API_SAMPLE -import pl.szczodrzynski.edziennik.api.v2.template.ENDPOINT_TEMPLATE_WEB_SAMPLE -import pl.szczodrzynski.edziennik.api.v2.template.ENDPOINT_TEMPLATE_WEB_SAMPLE_2 -import pl.szczodrzynski.edziennik.api.v2.template.data.api.TemplateApiSample -import pl.szczodrzynski.edziennik.api.v2.template.data.web.TemplateWebSample -import pl.szczodrzynski.edziennik.api.v2.template.data.web.TemplateWebSample2 -import pl.szczodrzynski.edziennik.utils.Utils - -class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "TemplateData" - } - - init { - nextEndpoint(onSuccess) - } - - private fun nextEndpoint(onSuccess: () -> Unit) { - if (data.targetEndpointIds.isEmpty()) { - onSuccess() - return - } - if (data.cancelled) { - onSuccess() - return - } - useEndpoint(data.targetEndpointIds.removeAt(0)) { - data.progress(data.progressStep) - nextEndpoint(onSuccess) - } - } - - private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { - Utils.d(TAG, "Using endpoint $endpointId") - when (endpointId) { - ENDPOINT_TEMPLATE_WEB_SAMPLE -> { - data.startProgress(R.string.edziennik_progress_endpoint_student_info) - TemplateWebSample(data) { onSuccess() } - } - ENDPOINT_TEMPLATE_WEB_SAMPLE_2 -> { - data.startProgress(R.string.edziennik_progress_endpoint_school_info) - TemplateWebSample2(data) { onSuccess() } - } - ENDPOINT_TEMPLATE_API_SAMPLE -> { - data.startProgress(R.string.edziennik_progress_endpoint_grades) - TemplateApiSample(data) { onSuccess() } - } - else -> onSuccess() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt deleted file mode 100644 index a6710b8c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-6. - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API -import pl.szczodrzynski.edziennik.api.v2.models.Data -import pl.szczodrzynski.edziennik.currentTimeUnix -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.isNotNullNorEmpty - -class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { - - fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix() - && apiCertificateKey.isNotNullNorEmpty() - && apiCertificatePrivate.isNotNullNorEmpty() - && symbol.isNotNullNorEmpty() - - override fun satisfyLoginMethods() { - loginMethods.clear() - if (isApiLoginValid()) { - loginMethods += LOGIN_METHOD_VULCAN_API - } - } - - /** - * A UONET+ client symbol. - * - * Present in the URL: https://uonetplus-uczen.vulcan.net.pl/[symbol]/[schoolSymbol]/ - * - * e.g. "poznan" - */ - private var mSymbol: String? = null - var symbol: String? - get() { mSymbol = mSymbol ?: loginStore.getLoginData("deviceSymbol", null); return mSymbol } - set(value) { loginStore.putLoginData("deviceSymbol", value); mSymbol = value } - - /** - * Group symbol/number of the student's school. - * - * Present in the URL: https://uonetplus-uczen.vulcan.net.pl/[symbol]/[schoolSymbol]/ - * - * ListaUczniow/JednostkaSprawozdawczaSymbol, e.g. "000088" - */ - private var mSchoolSymbol: String? = null - var schoolSymbol: String? - get() { mSchoolSymbol = mSchoolSymbol ?: profile?.getStudentData("schoolSymbol", null); return mSchoolSymbol } - set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value } - - /** - * A school ID consisting of the [symbol] and [schoolSymbol]. - * - * [symbol]_[schoolSymbol] - * - * e.g. "poznan_000088" - */ - private var mSchoolName: String? = null - var schoolName: String? - get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } - set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } - - /** - * ID of the student. - * - * ListaUczniow/Id, e.g. 42632 - */ - private var mStudentId: Int? = null - var studentId: Int - get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } - set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } - - /** - * ID of the student's account. - * - * ListaUczniow/UzytkownikLoginId, e.g. 1709 - */ - private var mStudentLoginId: Int? = null - var studentLoginId: Int - get() { mStudentLoginId = mStudentLoginId ?: profile?.getStudentData("studentLoginId", 0); return mStudentLoginId ?: 0 } - set(value) { profile?.putStudentData("studentLoginId", value) ?: return; mStudentLoginId = value } - - /** - * ID of the student's class. - * - * ListaUczniow/IdOddzial, e.g. 35 - */ - private var mStudentClassId: Int? = null - var studentClassId: Int - get() { mStudentClassId = mStudentClassId ?: profile?.getStudentData("studentClassId", 0); return mStudentClassId ?: 0 } - set(value) { profile?.putStudentData("studentClassId", value) ?: return; mStudentClassId = value } - - /** - * ListaUczniow/IdOkresKlasyfikacyjny, e.g. 321 - */ - private var mStudentSemesterId: Int? = null - var studentSemesterId: Int - get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 } - set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = value } - - /** - * ListaUczniow/OkresNumer, e.g. 1 or 2 - */ - private var mStudentSemesterNumber: Int? = null - var studentSemesterNumber: Int - get() { mStudentSemesterNumber = mStudentSemesterNumber ?: profile?.getStudentData("studentSemesterNumber", 0); return mStudentSemesterNumber ?: 0 } - set(value) { profile?.putStudentData("studentSemesterNumber", value) ?: return; mStudentSemesterNumber = value } - - /* _____ _____ ____ - /\ | __ \_ _| |___ \ - / \ | |__) || | __ ____) | - / /\ \ | ___/ | | \ \ / /__ < - / ____ \| | _| |_ \ V /___) | - /_/ \_\_| |_____| \_/|___*/ - /** - * A mobile API registration token. - * - * After first login only 3 first characters are stored here. - * This is later used to determine the API URL address. - */ - private var mApiToken: String? = null - var apiToken: String? - get() { mApiToken = mApiToken ?: loginStore.getLoginData("deviceToken", null); return mApiToken } - set(value) { loginStore.putLoginData("deviceToken", value); mApiToken = value } - - /** - * A mobile API registration PIN. - * - * After first login, this is removed and/or set to null. - */ - private var mApiPin: String? = null - var apiPin: String? - get() { mApiPin = mApiPin ?: loginStore.getLoginData("devicePin", null); return mApiPin } - set(value) { loginStore.putLoginData("devicePin", value); mApiPin = value } - - private var mApiCertificateKey: String? = null - var apiCertificateKey: String? - get() { mApiCertificateKey = mApiCertificateKey ?: loginStore.getLoginData("certificateKey", null); return mApiCertificateKey } - set(value) { loginStore.putLoginData("certificateKey", value); mApiCertificateKey = value } - - private var mApiCertificatePfx: String? = null - var apiCertificatePfx: String? - get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx } - set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value } - - private var mApiCertificatePrivate: String? = null - var apiCertificatePrivate: String? - get() { mApiCertificatePrivate = mApiCertificatePrivate ?: loginStore.getLoginData("certificatePrivate", null); return mApiCertificatePrivate } - set(value) { loginStore.putLoginData("certificatePrivate", value); mApiCertificatePrivate = value } - - private var mApiCertificateExpiryTime: Int? = null - var apiCertificateExpiryTime: Int - get() { mApiCertificateExpiryTime = mApiCertificateExpiryTime ?: loginStore.getLoginData("certificateExpiryTime", 0); return mApiCertificateExpiryTime ?: 0 } - set(value) { loginStore.putLoginData("certificateExpiryTime", value); mApiCertificateExpiryTime = value } - - val apiUrl: String? - get() { - val url = when (apiToken?.substring(0, 3)) { - "3S1" -> "https://lekcjaplus.vulcan.net.pl" - "TA1" -> "https://uonetplus-komunikacja.umt.tarnow.pl" - "OP1" -> "https://uonetplus-komunikacja.eszkola.opolskie.pl" - "RZ1" -> "https://uonetplus-komunikacja.resman.pl" - "GD1" -> "https://uonetplus-komunikacja.edu.gdansk.pl" - "KA1" -> "https://uonetplus-komunikacja.mcuw.katowice.eu" - "KA2" -> "https://uonetplus-komunikacja-test.mcuw.katowice.eu" - "P03" -> "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl" - "P01" -> "http://efeb-komunikacja.pro-hudson.win.vulcan.pl" - "P02" -> "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl" - "P90" -> "http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl" - "FK1", "FS1" -> "http://api.fakelog.cf" - "SZ9" -> "http://vulcan.szkolny.eu" - else -> null - } - return if (url != null) "$url/$symbol" else null - } - - val fullApiUrl: String? - get() { - return "$apiUrl/$schoolSymbol" - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/Vulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/Vulcan.kt deleted file mode 100644 index b812fca8..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/Vulcan.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-6. - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan - -import com.google.gson.JsonObject -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.prepare -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanData -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.api.VulcanApiMessagesChangeStatus -import pl.szczodrzynski.edziennik.api.v2.vulcan.firstlogin.VulcanFirstLogin -import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin -import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi -import pl.szczodrzynski.edziennik.api.v2.vulcanLoginMethods -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.utils.Utils.d - -class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { - companion object { - private const val TAG = "Vulcan" - } - - val internalErrorList = mutableListOf() - val data: DataVulcan - - init { - data = DataVulcan(app, profile, loginStore).apply { - callback = wrapCallback(this@Vulcan.callback) - satisfyLoginMethods() - } - } - - private fun completed() { - data.saveData() - data.notifyAndSyncEvents { - callback.onCompleted() - } - } - - /* _______ _ _ _ _ _ - |__ __| | /\ | | (_) | | | - | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ - | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ - | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | - |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| - __/ | - |__*/ - override fun sync(featureIds: List, viewId: Int?, arguments: JsonObject?) { - data.arguments = arguments - data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId) - d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") - d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") - VulcanLogin(data) { - VulcanData(data) { - completed() - } - } - } - - override fun getMessage(message: MessageFull) { - VulcanLoginApi(data) { - VulcanApiMessagesChangeStatus(data, message) { - completed() - } - } - } - - override fun markAllAnnouncementsAsRead() { - - } - - override fun firstLogin() { - VulcanFirstLogin(data) { - completed() - } - } - - override fun cancel() { - d(TAG, "Cancelled") - data.cancel() - } - - private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { - return object : EdziennikCallback { - override fun onCompleted() { - callback.onCompleted() - } - - override fun onProgress(step: Float) { - callback.onProgress(step) - } - - override fun onStartProgress(stringRes: Int) { - callback.onStartProgress(stringRes) - } - - override fun onError(apiError: ApiError) { - when (apiError.errorCode) { - in internalErrorList -> { - // finish immediately if the same error occurs twice during the same sync - callback.onError(apiError) - } - CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { - internalErrorList.add(apiError.errorCode) - loginStore.removeLoginData("refreshToken") // force a clean login - //loginLibrus() - } - else -> callback.onError(apiError) - } - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiAttendance.kt deleted file mode 100644 index 174eed18..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiAttendance.kt +++ /dev/null @@ -1,78 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import androidx.core.util.isEmpty -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_ATTENDANCE -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_ATTENDANCE -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance -import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_PRESENT -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.utils.models.Date - -class VulcanApiAttendance(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiAttendance" - } - - init { data.profile?.also { profile -> - if (data.attendanceTypes.isEmpty()) { - data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } - } - - val startDate: String = profile.getSemesterStart(profile.currentSemester).stringY_m_d - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d - - apiGet(TAG, VULCAN_API_ENDPOINT_ATTENDANCE, parameters = mapOf( - "DataPoczatkowa" to startDate, - "DataKoncowa" to endDate, - "IdOddzial" to data.studentClassId, - "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId - )) { json, _ -> - json.getJsonObject("Data")?.getJsonArray("Frekwencje")?.forEach { attendanceEl -> - val attendance = attendanceEl.asJsonObject - - val attendanceCategory = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach) - ?: return@forEach - - val type = attendanceCategory.type - - val id = (attendance.getInt("Dzien") ?: 0) + (attendance.getInt("Numer") ?: 0) - - val lessonDateMillis = Date.fromY_m_d(attendance.getString("DzienTekst")).inMillis - val lessonDate = Date.fromMillis(lessonDateMillis) - - val lessonSemester = profile.dateToSemester(lessonDate) - - val attendanceObject = Attendance( - profileId, - id.toLong(), - -1, - attendance.getLong("IdPrzedmiot") ?: -1, - lessonSemester, - attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" }, - lessonDate, - data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime, - type) - - data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_ATTENDANCE, - attendanceObject.id, - profile.empty, - profile.empty, - attendanceObject.lessonDate.combineWith(attendanceObject.startTime) - )) - } - } - - data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS) - onSuccess() - } - } ?: onSuccess()} -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiEvents.kt deleted file mode 100644 index d971118c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiEvents.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-20 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_EVENTS -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_HOMEWORK -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_EVENTS -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_HOMEWORK -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getBoolean -import pl.szczodrzynski.edziennik.getJsonArray -import pl.szczodrzynski.edziennik.getLong -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.utils.models.Date - -class VulcanApiEvents(override val data: DataVulcan, private val isHomework: Boolean, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiEvents" - } - - init { data.profile?.also { profile -> - - val startDate: String = when (profile.empty) { - true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d - else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d - } - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d - - val endpoint = when (isHomework) { - true -> VULCAN_API_ENDPOINT_HOMEWORK - else -> VULCAN_API_ENDPOINT_EVENTS - } - apiGet(TAG, endpoint, parameters = mapOf( - "DataPoczatkowa" to startDate, - "DataKoncowa" to endDate, - "IdOddzial" to data.studentClassId, - "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId - )) { json, _ -> - val events = json.getJsonArray("Data") - - events?.forEach { eventEl -> - val event = eventEl.asJsonObject - - val id = event?.getLong("Id") ?: return@forEach - val eventDate = Date.fromY_m_d(event.getString("DataTekst") ?: return@forEach) - val subjectId = event.getLong("IdPrzedmiot") ?: -1 - val teacherId = event.getLong("IdPracownik") ?: -1 - val startTime = data.lessonList.singleOrNull { - it.weekDay == eventDate.weekDay && it.subjectId == subjectId - }?.startTime - val topic = event.getString("Opis") ?: "" - val type = when (isHomework) { - true -> Event.TYPE_HOMEWORK - else -> when (event.getBoolean("Rodzaj")) { - false -> Event.TYPE_SHORT_QUIZ - else -> Event.TYPE_EXAM - } - } - val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1 - - val eventObject = Event( - profileId, - id, - eventDate, - startTime, - topic, - -1, - type, - false, - teacherId, - subjectId, - teamId - ) - - data.eventList.add(eventObject) - data.metadataList.add(Metadata( - profileId, - if (isHomework) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, - id, - profile.empty, - profile.empty, - System.currentTimeMillis() - )) - } - - when (isHomework) { - true -> data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS) - false -> data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS) - } - onSuccess() - } - } ?: onSuccess()} -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiMessagesChangeStatus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiMessagesChangeStatus.kt deleted file mode 100644 index 098edfb2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiMessagesChangeStatus.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-11-12 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata - -class VulcanApiMessagesChangeStatus( - override val data: DataVulcan, - private val messageObject: MessageFull, - val onSuccess: () -> Unit -) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiMessagesChangeStatus" - } - - init { - data.profile?.also { profile -> - apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS, parameters = mapOf( - "WiadomoscId" to messageObject.id, - "FolderWiadomosci" to "Odebrane", - "Status" to "Widoczna", - "LoginId" to data.studentLoginId, - "IdUczen" to data.studentId - )) { _, _ -> - - if (!messageObject.seen) { - data.messageMetadataList.add(Metadata( - profileId, - Metadata.TYPE_MESSAGE, - messageObject.id, - true, - true, - messageObject.addedDate - )) - } - - if (messageObject.type != TYPE_SENT) { - val messageRecipientObject = MessageRecipient( - profileId, - -1, - -1, - System.currentTimeMillis(), - messageObject.id - ) - - data.messageRecipientList.add(messageRecipientObject) - } - - onSuccess() - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiNotices.kt deleted file mode 100644 index 6407dddd..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiNotices.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-23 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import androidx.core.util.isEmpty -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_NOTICES -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_NOTICES -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice -import pl.szczodrzynski.edziennik.getJsonArray -import pl.szczodrzynski.edziennik.getLong -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.toSparseArray -import pl.szczodrzynski.edziennik.utils.models.Date - -class VulcanApiNotices(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiNotices" - } - - init { data.profile?.also { profile -> - if (data.noticeTypes.isEmpty()) { - data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id } - } - - apiGet(TAG, VULCAN_API_ENDPOINT_NOTICES, parameters = mapOf( - "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId - )) { json, _ -> - json.getJsonArray("Data")?.forEach { noticeEl -> - val notice = noticeEl.asJsonObject - - val id = notice.getLong("Id") ?: return@forEach - val text = notice.getString("TrescUwagi") ?: return@forEach - val teacherId = notice.getLong("IdPracownik") ?: -1 - val addedDate = Date.fromY_m_d(notice.getString("DataWpisuTekst")).inMillis - - val noticeObject = Notice( - profileId, - id, - text, - profile.currentSemester, - Notice.TYPE_NEUTRAL, - teacherId - ) - - data.noticeList.add(noticeObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_NOTICE, - id, - profile.empty, - profile.empty, - addedDate - )) - } - - data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS) - onSuccess() - } - } ?: onSuccess()} -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiProposedGrades.kt deleted file mode 100644 index 8289a549..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiProposedGrades.kt +++ /dev/null @@ -1,81 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import com.google.gson.JsonArray -import pl.szczodrzynski.edziennik.HOUR -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_GRADES_SUMMARY -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.asJsonObjectList -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getJsonArray -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.utils.Utils - -class VulcanApiProposedGrades(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiProposedGrades" - } - - init { data.profile?.also { profile -> - - apiGet(TAG, VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS, parameters = mapOf( - "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId - )) { json, _ -> - val grades = json.getJsonObject("Data") - - grades.getJsonArray("OcenyPrzewidywane")?.let { - processGradeList(it, isFinal = false) - } - - grades.getJsonArray("OcenyKlasyfikacyjne")?.let { - processGradeList(it, isFinal = true) - } - - data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR) - onSuccess() - } - } ?: onSuccess()} - - private fun processGradeList(grades: JsonArray, isFinal: Boolean) { - grades.asJsonObjectList()?.forEach { grade -> - val name = grade.get("Wpis").asString - val value = Utils.getGradeValue(name) - val subjectId = grade.get("IdPrzedmiot").asLong - - val id = subjectId * -100 - data.studentSemesterNumber - - val color = Utils.getVulcanGradeColor(name) - - val gradeObject = Grade( - profileId, - id, - "", - color, - "", - name, - value, - 0f, - data.studentSemesterNumber, - -1, - subjectId - ) - if (data.studentSemesterNumber == 1) { - gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER1_PROPOSED - } else { - gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER2_FINAL else Grade.TYPE_SEMESTER2_PROPOSED - } - data.gradeList.add(gradeObject) - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_GRADE, - gradeObject.id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() - )) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTemplate.kt deleted file mode 100644 index c62a0de8..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTemplate.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-20 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi - -class VulcanApiTemplate(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApi" - } - - init { - /* data.profile?.also { profile -> - apiGet(TAG, VULCAN_API_ENDPOINT_) { json, _ -> - - data.setSyncNext(ENDPOINT_VULCAN_API_, SYNC_ALWAYS) - onSuccess() - } - } */ - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTimetable.kt deleted file mode 100644 index a7a2c83e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/api/VulcanApiTimetable.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-11-13 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api - -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.Regexes -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE -import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_TIMETABLE -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject -import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson -import pl.szczodrzynski.edziennik.utils.Utils.crc16 -import pl.szczodrzynski.edziennik.utils.Utils.d -import pl.szczodrzynski.edziennik.utils.models.Date - -class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { - companion object { - const val TAG = "VulcanApiTimetable" - } - - init { - data.profile?.also { profile -> - val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) } - val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d - - val weekStart = Date.fromY_m_d(getDate) - val weekEnd = weekStart.clone().stepForward(0, 0, 6) - - apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf( - "DataPoczatkowa" to weekStart.stringY_m_d, - "DataKoncowa" to weekEnd.stringY_m_d, - "IdUczen" to data.studentId, - "IdOddzial" to data.studentClassId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId - )) { json, _ -> - val dates: MutableSet = mutableSetOf() - val lessons: MutableList = mutableListOf() - - json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson -> - val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst")) - val lessonNumber = lesson.getInt("NumerLekcji") - val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber } - val startTime = lessonRange?.startTime - val endTime = lessonRange?.endTime - val teacherId = lesson.getLong("IdPracownik") - val teamId = data.studentClassId.toLong() - val classroom = lesson.getString("Classroom") - - val oldTeacherId = lesson.getLong("IdPracownikOld") - - val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: "" - val type = when { - changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET - changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE - changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE - lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED - else -> Lesson.TYPE_NORMAL - } - - val subjectId = lesson.getLong("IdPrzedmiot")?.let { - when (it) { - 0L -> { - val subjectName = lesson.getString("PrzedmiotNazwa") ?: "" - - data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id - ?: { - /** - * CREATE A NEW SUBJECT IF IT DOESN'T EXIST - */ - - val subjectObject = Subject( - profileId, - -1 * crc16(subjectName.toByteArray()).toLong(), - subjectName, - subjectName - ) - data.subjectList.put(subjectObject.id, subjectObject) - subjectObject.id - }.invoke() - } - else -> it - } - } - - val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF) - - val lessonObject = Lesson(profileId, id).apply { - this.type = type - - when (type) { - Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> { - this.date = lessonDate - this.lessonNumber = lessonNumber - this.startTime = startTime - this.endTime = endTime - this.subjectId = subjectId - this.teacherId = teacherId - this.teamId = teamId - this.classroom = classroom - - this.oldTeacherId = oldTeacherId - } - - Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> { - this.oldDate = lessonDate - this.oldLessonNumber = lessonNumber - this.oldStartTime = startTime - this.oldEndTime = endTime - this.oldSubjectId = subjectId - this.oldTeacherId = teacherId - this.oldTeamId = teamId - this.oldClassroom = classroom - } - } - - if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) { - val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation) - val oldLessonNumber = shift?.get(2)?.toInt() - val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) } - - val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber } - val oldStartTime = oldLessonRange?.startTime - val oldEndTime = oldLessonRange?.endTime - - when (type) { - Lesson.TYPE_SHIFTED_SOURCE -> { - this.lessonNumber = oldLessonNumber - this.date = oldLessonDate - this.startTime = oldStartTime - this.endTime = oldEndTime - } - - Lesson.TYPE_SHIFTED_TARGET -> { - this.oldLessonNumber = oldLessonNumber - this.oldDate = oldLessonDate - this.oldStartTime = oldStartTime - this.oldEndTime = oldEndTime - } - } - } - } - - if (type != Lesson.TYPE_NORMAL) { - data.metadataList.add(Metadata( - profileId, - Metadata.TYPE_LESSON_CHANGE, - id, - profile.empty, - profile.empty, - System.currentTimeMillis() - )) - } - - dates.add(lessonDate.value) - lessons.add(lessonObject) - } - - val date: Date = weekStart.clone() - while (date <= weekEnd) { - if (!dates.contains(date.value)) { - lessons.add(Lesson(profileId, date.value.toLong()).apply { - this.type = Lesson.TYPE_NO_LESSONS - this.date = date.clone() - }) - } - - date.stepForward(0, 0, 1) - } - - d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") - - data.lessonNewList.addAll(lessons) - data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) - - data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS) - onSuccess() - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/firstlogin/VulcanFirstLogin.kt deleted file mode 100644 index 61f38f53..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/firstlogin/VulcanFirstLogin.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-10-19 - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.firstlogin - -import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT -import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_STUDENT_LIST -import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi -import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.utils.models.Date - -class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { - companion object { - const val TAG = "VulcanFirstLogin" - } - - private val api = VulcanApi(data) - private val profileList = mutableListOf() - - init { - VulcanLoginApi(data) { - api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, response -> - val students = json.getJsonArray("Data") - - if (students == null || students.size() < 1) { - data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT) - .withResponse(response) - .withApiResponse(json)) - return@apiGet - } - - students.forEach { studentEl -> - val student = studentEl.asJsonObject - - val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach - val schoolName = "${data.symbol}_$schoolSymbol" - val studentId = student.getInt("Id") ?: return@forEach - val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach - val studentClassId = student.getInt("IdOddzial") ?: return@forEach - val studentClassName = student.getString("OkresPoziom").toString() + (student.getString("OddzialSymbol") ?: return@forEach) - val studentSemesterId = student.getInt("IdOkresKlasyfikacyjny") ?: return@forEach - val studentFirstName = student.getString("Imie") ?: "" - val studentLastName = student.getString("Nazwisko") ?: "" - val studentNameLong = "$studentFirstName $studentLastName".fixName() - val studentNameShort = "$studentFirstName ${studentLastName[0]}.".fixName() - - val userLogin = student.getString("UzytkownikLogin") ?: "" - val currentSemesterStartDate = student.getLong("OkresDataOd") ?: return@forEach - val currentSemesterEndDate = (student.getLong("OkresDataDo") - ?: return@forEach) + 86400 - val studentSemesterNumber = student.getInt("OkresNumer") ?: return@forEach - - val newProfile = Profile() - newProfile.empty = true - - val isParent = student.getString("UzytkownikRola") == "opiekun" - val userName = if (isParent) - student.getString("UzytkownikNazwa")?.swapFirstLastName()?.fixName() - else - null - newProfile.accountNameLong = userName - newProfile.studentClassName = studentClassName - val today = Date.getToday() - newProfile.studentSchoolYear = "${today.year}/${today.year+1}" - - newProfile.putStudentData("studentId", studentId) - newProfile.putStudentData("studentLoginId", studentLoginId) - newProfile.putStudentData("studentClassId", studentClassId) - newProfile.putStudentData("studentSemesterId", studentSemesterId) - newProfile.putStudentData("schoolSymbol", schoolSymbol) - newProfile.putStudentData("schoolName", schoolName) - newProfile.putStudentData("currentSemesterEndDate", currentSemesterEndDate) - newProfile.putStudentData("studentSemesterNumber", studentSemesterNumber) - - when (studentSemesterNumber) { - 1 -> { - newProfile.dateSemester1Start = Date.fromMillis(currentSemesterStartDate * 1000) - newProfile.dateSemester2Start = Date.fromMillis(currentSemesterEndDate * 1000) - } - 2 -> { - newProfile.dateSemester2Start = Date.fromMillis(currentSemesterStartDate * 1000) - newProfile.dateYearEnd = Date.fromMillis(currentSemesterEndDate * 1000) - } - } - - newProfile.studentNameLong = studentNameLong - newProfile.studentNameShort = studentNameShort - newProfile.name = studentNameLong - newProfile.subname = userLogin - - profileList.add(newProfile) - } - - EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) - onSuccess() - } - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt deleted file mode 100644 index ee81cb9e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-10-6. - */ - -package pl.szczodrzynski.edziennik.api.v2.vulcan.login - -import android.os.Build -import com.google.gson.JsonObject -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import im.wangchao.mhttp.callback.JsonCallbackHandler -import io.github.wulkanowy.signer.android.getPrivateKeyFromCert -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan -import pl.szczodrzynski.edziennik.currentTimeUnix -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.isNotNullNorEmpty -import pl.szczodrzynski.edziennik.utils.Utils.d -import java.net.HttpURLConnection.HTTP_BAD_REQUEST -import java.util.* -import java.util.regex.Pattern - -class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "VulcanLoginApi" - } - - init { run { - if (data.profile != null && data.isApiLoginValid()) { - onSuccess() - } - else { - if (data.apiCertificatePfx.isNotNullNorEmpty()) { - try { - data.apiCertificatePrivate = getPrivateKeyFromCert( - if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, - data.apiCertificatePfx ?: "" - ) - onSuccess() - return@run - } catch (e: Throwable) { - e.printStackTrace() - } - } - if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) { - loginWithToken() - } - else { - data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) - } - } - }} - - private fun loginWithToken() { - d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE") - - val callback = object : JsonCallbackHandler() { - override fun onSuccess(json: JsonObject?, response: Response?) { - if (json == null) { - if (response?.code() == HTTP_BAD_REQUEST) { - data.error(TAG, ERROR_LOGIN_VULCAN_INVALID_SYMBOL, response) - return - } - data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) - .withResponse(response)) - return - } - - var tokenStatus = json.getString("TokenStatus") - if (tokenStatus == "Null" || tokenStatus == "CertGenerated") - tokenStatus = null - val error = tokenStatus ?: json.getString("Message") - error?.let { code -> - when (code) { - "TokenNotFound" -> ERROR_LOGIN_VULCAN_INVALID_TOKEN - "TokenDead" -> ERROR_LOGIN_VULCAN_EXPIRED_TOKEN - "WrongPIN" -> { - Pattern.compile("Liczba pozostałych prób: ([0-9])", Pattern.DOTALL).matcher(tokenStatus).let { matcher -> - if (matcher.matches()) - ERROR_LOGIN_VULCAN_INVALID_PIN + 1 + matcher.group(1).toInt() - else - ERROR_LOGIN_VULCAN_INVALID_PIN - } - } - "Broken" -> ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING - "OnlyKindergarten" -> ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN - "NoPupils" -> ERROR_LOGIN_VULCAN_NO_PUPILS - else -> ERROR_LOGIN_VULCAN_OTHER - }.let { errorCode -> - data.error(ApiError(TAG, errorCode) - .withApiResponse(json) - .withResponse(response)) - return - } - } - - val cert = json.getJsonObject("TokenCert") - if (cert == null) { - data.error(ApiError(TAG, ERROR_LOGIN_VULCAN_OTHER) - .withApiResponse(json) - .withResponse(response)) - return - } - - data.apiCertificateKey = cert.getString("CertyfikatKlucz") - data.apiCertificatePfx = cert.getString("CertyfikatPfx") - data.apiCertificateExpiryTime = 1598832000 - data.apiToken = data.apiToken?.substring(0, 3) - data.apiCertificatePrivate = getPrivateKeyFromCert( - if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, - data.apiCertificatePfx ?: "" - ) - data.loginStore.removeLoginData("certificatePfx") - data.loginStore.removeLoginData("devicePin") - onSuccess() - } - - override fun onFailure(response: Response?, throwable: Throwable?) { - data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) - .withResponse(response) - .withThrowable(throwable)) - } - } - - Request.builder() - .url("${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE") - .userAgent(VULCAN_API_USER_AGENT) - .addHeader("RequestMobileType", "RegisterDevice") - .addParameter("PIN", data.apiPin) - .addParameter("TokenKey", data.apiToken) - .addParameter("DeviceId", UUID.randomUUID().toString()) - .addParameter("DeviceName", VULCAN_API_DEVICE_NAME) - .addParameter("DeviceNameUser", "") - .addParameter("DeviceDescription", "") - .addParameter("DeviceSystemType", "Android") - .addParameter("DeviceSystemVersion", Build.VERSION.RELEASE) - .addParameter("RemoteMobileTimeKey", currentTimeUnix()) - .addParameter("TimeKey", currentTimeUnix() - 1) - .addParameter("RequestId", UUID.randomUUID().toString()) - .addParameter("AppVersion", VULCAN_API_APP_VERSION) - .addParameter("RemoteMobileAppVersion", VULCAN_API_APP_VERSION) - .addParameter("RemoteMobileAppName", VULCAN_API_APP_NAME) - .postJson() - .allowErrorCode(HTTP_BAD_REQUEST) - .callback(callback) - .build() - .enqueue() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt new file mode 100644 index 00000000..d597a119 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +interface AbstractConfig { + fun set(key: String, value: String?) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt new file mode 100644 index 00000000..98240be5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import com.google.gson.JsonObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.config.utils.ConfigMigration +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.config.utils.toHashMap +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.data.db.AppDb +import kotlin.coroutines.CoroutineContext + +class Config(val db: AppDb) : CoroutineScope, AbstractConfig { + companion object { + const val DATA_VERSION = 12 + } + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val values: HashMap = hashMapOf() + + val ui by lazy { ConfigUI(this) } + val sync by lazy { ConfigSync(this) } + val timetable by lazy { ConfigTimetable(this) } + val grades by lazy { ConfigGrades(this) } + + private var mDataVersion: Int? = null + var dataVersion: Int + get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 } + set(value) { set("dataVersion", value); mDataVersion = value } + + private var mHash: String? = null + var hash: String + get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } + set(value) { set("hash", value); mHash = value } + + private var mLastProfileId: Int? = null + var lastProfileId: Int + get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 } + set(value) { set("lastProfileId", value); mLastProfileId = value } + + private var mUpdatesChannel: String? = null + var updatesChannel: String + get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" } + set(value) { set("updatesChannel", value); mUpdatesChannel = value } + private var mUpdate: Update? = null + var update: Update? + get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? } + set(value) { set("update", value); mUpdate = value } + + private var mAppVersion: Int? = null + var appVersion: Int + get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE } + set(value) { set("appVersion", value); mAppVersion = value } + + private var mLoginFinished: Boolean? = null + var loginFinished: Boolean + get() { mLoginFinished = mLoginFinished ?: values.get("loginFinished", false); return mLoginFinished ?: false } + set(value) { set("loginFinished", value); mLoginFinished = value } + + private var mPrivacyPolicyAccepted: Boolean? = null + var privacyPolicyAccepted: Boolean + get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false } + set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value } + + private var mDebugMode: Boolean? = null + var debugMode: Boolean + get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false } + set(value) { set("debugMode", value); mDebugMode = value } + + private var mDevModePassword: String? = null + var devModePassword: String? + get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword } + set(value) { set("devModePassword", value); mDevModePassword = value } + + private var mAppInstalledTime: Long? = null + var appInstalledTime: Long + get() { mAppInstalledTime = mAppInstalledTime ?: values.get("appInstalledTime", 0L); return mAppInstalledTime ?: 0L } + set(value) { set("appInstalledTime", value); mAppInstalledTime = value } + + private var mAppRateSnackbarTime: Long? = null + var appRateSnackbarTime: Long + get() { mAppRateSnackbarTime = mAppRateSnackbarTime ?: values.get("appRateSnackbarTime", 0L); return mAppRateSnackbarTime ?: 0L } + set(value) { set("appRateSnackbarTime", value); mAppRateSnackbarTime = value } + + private var mRunSync: Boolean? = null + var runSync: Boolean + get() { mRunSync = mRunSync ?: values.get("runSync", false); return mRunSync ?: false } + set(value) { set("runSync", value); mRunSync = value } + + private var mWidgetConfigs: JsonObject? = null + var widgetConfigs: JsonObject + get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() } + set(value) { set("widgetConfigs", value); mWidgetConfigs = value } + + private var rawEntries: List = db.configDao().getAllNow() + private val profileConfigs: HashMap = hashMapOf() + init { + rawEntries.toHashMap(-1, values) + } + fun migrate(app: App) { + if (dataVersion < DATA_VERSION) + ConfigMigration(app, this) + } + fun getFor(profileId: Int): ProfileConfig { + return profileConfigs[profileId] ?: ProfileConfig(db, profileId, db.configDao().getAllNow(profileId)).also { + profileConfigs[profileId] = it + } + } + fun forProfile() = getFor(App.profileId) + + fun setProfile(profileId: Int) { + } + + override fun set(key: String, value: String?) { + values[key] = value + launch { + db.configDao().add(ConfigEntry(-1, key, value)) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt new file mode 100644 index 00000000..267ec322 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.managers.GradesManager + +class ConfigGrades(private val config: Config) { + private var mOrderBy: Int? = null + var orderBy: Int + get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC } + set(value) { config.set("gradesOrderBy", value); mOrderBy = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt new file mode 100644 index 00000000..2d7dcf78 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getIntList +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.config.utils.setMap +import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigSync(private val config: Config) { + private val gson = Gson() + + private var mDontShowAppManagerDialog: Boolean? = null + var dontShowAppManagerDialog: Boolean + get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false } + set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value } + + private var mSyncEnabled: Boolean? = null + var enabled: Boolean + get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true } + set(value) { config.set("syncEnabled", value); mSyncEnabled = value } + + private var mWebPushEnabled: Boolean? = null + var webPushEnabled: Boolean + get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true } + set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value } + + private var mSyncOnlyWifi: Boolean? = null + var onlyWifi: Boolean + get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates } + set(value) { config.set("syncOnlyWifi", value); mSyncOnlyWifi = value } + + private var mSyncInterval: Int? = null + var interval: Int + get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 } + set(value) { config.set("syncInterval", value); mSyncInterval = value } + + private var mNotifyAboutUpdates: Boolean? = null + var notifyAboutUpdates: Boolean + get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true } + set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value } + + private var mLastAppSync: Long? = null + var lastAppSync: Long + get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L } + set(value) { config.set("lastAppSync", value); mLastAppSync = value } + + /* ____ _ _ _ + / __ \ (_) | | | | + | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___ + | | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __| + | |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \ + \___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/ + private var mQuietHoursEnabled: Boolean? = null + var quietHoursEnabled: Boolean + get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false } + set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value } + + private var mQuietHoursStart: Time? = null + var quietHoursStart: Time? + get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart } + set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value } + + private var mQuietHoursEnd: Time? = null + var quietHoursEnd: Time? + get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd } + set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value } + + private var mQuietDuringLessons: Boolean? = null + var quietDuringLessons: Boolean + get() { mQuietDuringLessons = mQuietDuringLessons ?: config.values.get("quietDuringLessons", false); return mQuietDuringLessons ?: false } + set(value) { config.set("quietDuringLessons", value); mQuietDuringLessons = value } + + /* ______ _____ __ __ _______ _ + | ____/ ____| \/ | |__ __| | | + | |__ | | | \ / | | | ___ | | _____ _ __ ___ + | __|| | | |\/| | | |/ _ \| |/ / _ \ '_ \/ __| + | | | |____| | | | | | (_) | < __/ | | \__ \ + |_| \_____|_| |_| |_|\___/|_|\_\___|_| |_|__*/ + private var mTokenApp: String? = null + var tokenApp: String? + get() { mTokenApp = mTokenApp ?: config.values.get("tokenApp", null as String?); return mTokenApp } + set(value) { config.set("tokenApp", value); mTokenApp = value } + private var mTokenMobidziennik: String? = null + var tokenMobidziennik: String? + get() { mTokenMobidziennik = mTokenMobidziennik ?: config.values.get("tokenMobidziennik", null as String?); return mTokenMobidziennik } + set(value) { config.set("tokenMobidziennik", value); mTokenMobidziennik = value } + private var mTokenLibrus: String? = null + var tokenLibrus: String? + get() { mTokenLibrus = mTokenLibrus ?: config.values.get("tokenLibrus", null as String?); return mTokenLibrus } + set(value) { config.set("tokenLibrus", value); mTokenLibrus = value } + private var mTokenVulcan: String? = null + var tokenVulcan: String? + get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan } + set(value) { config.set("tokenVulcan", value); mTokenVulcan = value } + + private var mTokenMobidziennikList: List? = null + var tokenMobidziennikList: List + get() { mTokenMobidziennikList = mTokenMobidziennikList ?: config.values.getIntList("tokenMobidziennikList", listOf()); return mTokenMobidziennikList ?: listOf() } + set(value) { config.set("tokenMobidziennikList", value); mTokenMobidziennikList = value } + private var mTokenLibrusList: List? = null + var tokenLibrusList: List + get() { mTokenLibrusList = mTokenLibrusList ?: config.values.getIntList("tokenLibrusList", listOf()); return mTokenLibrusList ?: listOf() } + set(value) { config.set("tokenLibrusList", value); mTokenLibrusList = value } + private var mTokenVulcanList: List? = null + var tokenVulcanList: List + get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() } + set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value } + + private var mRegisterAvailability: Map? = null + var registerAvailability: Map + get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson>(it, object: TypeToken>(){}.type) }; return mRegisterAvailability ?: mapOf() } + set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt new file mode 100644 index 00000000..2418f1d3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigTimetable(private val config: Config) { + private var mBellSyncMultiplier: Int? = null + var bellSyncMultiplier: Int + get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 } + set(value) { config.set("bellSyncMultiplier", value); mBellSyncMultiplier = value } + + private var mBellSyncDiff: Time? = null + var bellSyncDiff: Time? + get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff } + set(value) { config.set("bellSyncDiff", value); mBellSyncDiff = value } + + private var mCountInSeconds: Boolean? = null + var countInSeconds: Boolean + get() { mCountInSeconds = mCountInSeconds ?: config.values.get("countInSeconds", false); return mCountInSeconds ?: false } + set(value) { config.set("countInSeconds", value); mCountInSeconds = value } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt new file mode 100644 index 00000000..7b39383e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getIntList +import pl.szczodrzynski.edziennik.config.utils.set + +class ConfigUI(private val config: Config) { + private var mTheme: Int? = null + var theme: Int + get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 } + set(value) { config.set("theme", value); mTheme = value } + + private var mLanguage: String? = null + var language: String? + get() { mLanguage = mLanguage ?: config.values.get("language", null as String?); return mLanguage } + set(value) { config.set("language", value); mLanguage = value } + + private var mHeaderBackground: String? = null + var headerBackground: String? + get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBg", null as String?); return mHeaderBackground } + set(value) { config.set("headerBg", value); mHeaderBackground = value } + + private var mAppBackground: String? = null + var appBackground: String? + get() { mAppBackground = mAppBackground ?: config.values.get("appBg", null as String?); return mAppBackground } + set(value) { config.set("appBg", value); mAppBackground = value } + + private var mMiniMenuVisible: Boolean? = null + var miniMenuVisible: Boolean + get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false } + set(value) { config.set("miniMenuVisible", value); mMiniMenuVisible = value } + + private var mMiniMenuButtons: List? = null + var miniMenuButtons: List + get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() } + set(value) { config.set("miniMenuButtons", value); mMiniMenuButtons = value } + + private var mOpenDrawerOnBackPressed: Boolean? = null + var openDrawerOnBackPressed: Boolean + get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false } + set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value } + + private var mSnowfall: Boolean? = null + var snowfall: Boolean + get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } + set(value) { config.set("snowfall", value); mSnowfall = value } + + private var mBottomSheetOpened: Boolean? = null + var bottomSheetOpened: Boolean + get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } + set(value) { config.set("bottomSheetOpened", value); mBottomSheetOpened = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt new file mode 100644 index 00000000..c4c65ac3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.config.utils.ProfileConfigMigration +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.config.utils.toHashMap +import pl.szczodrzynski.edziennik.data.db.AppDb +import kotlin.coroutines.CoroutineContext + +class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List) : CoroutineScope, AbstractConfig { + companion object { + const val DATA_VERSION = 2 + } + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val values: HashMap = hashMapOf() + + val grades by lazy { ProfileConfigGrades(this) } + val ui by lazy { ProfileConfigUI(this) } + val sync by lazy { ProfileConfigSync(this) } + val attendance by lazy { ProfileConfigAttendance(this) } + /* + val timetable by lazy { ConfigTimetable(this) } + val grades by lazy { ConfigGrades(this) }*/ + + private var mDataVersion: Int? = null + var dataVersion: Int + get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 } + set(value) { set("dataVersion", value); mDataVersion = value } + + private var mHash: String? = null + var hash: String + get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } + set(value) { set("hash", value); mHash = value } + + init { + rawEntries.toHashMap(profileId, values) + if (dataVersion < DATA_VERSION) + ProfileConfigMigration(this) + } + + override fun set(key: String, value: String?) { + values[key] = value + launch { + db.configDao().add(ConfigEntry(profileId, key, value)) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt new file mode 100644 index 00000000..326f5b16 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set + +class ProfileConfigAttendance(private val config: ProfileConfig) { + private var mAttendancePageSelection: Int? = null + var attendancePageSelection: Int + get() { mAttendancePageSelection = mAttendancePageSelection ?: config.values.get("attendancePageSelection", 1); return mAttendancePageSelection ?: 1 } + set(value) { config.set("attendancePageSelection", value); mAttendancePageSelection = value } + + private var mUseSymbols: Boolean? = null + var useSymbols: Boolean + get() { mUseSymbols = mUseSymbols ?: config.values.get("useSymbols", false); return mUseSymbols ?: false } + set(value) { config.set("useSymbols", value); mUseSymbols = value } + + private var mGroupConsecutiveDays: Boolean? = null + var groupConsecutiveDays: Boolean + get() { mGroupConsecutiveDays = mGroupConsecutiveDays ?: config.values.get("groupConsecutiveDays", true); return mGroupConsecutiveDays ?: true } + set(value) { config.set("groupConsecutiveDays", value); mGroupConsecutiveDays = value } + + private var mShowPresenceInMonth: Boolean? = null + var showPresenceInMonth: Boolean + get() { mShowPresenceInMonth = mShowPresenceInMonth ?: config.values.get("showPresenceInMonth", false); return mShowPresenceInMonth ?: false } + set(value) { config.set("showPresenceInMonth", value); mShowPresenceInMonth = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt new file mode 100644 index 00000000..490fe2dd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getFloat +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES + +class ProfileConfigGrades(private val config: ProfileConfig) { + private var mColorMode: Int? = null + var colorMode: Int + get() { mColorMode = mColorMode ?: config.values.get("gradesColorMode", COLOR_MODE_WEIGHTED); return mColorMode ?: COLOR_MODE_WEIGHTED } + set(value) { config.set("gradesColorMode", value); mColorMode = value } + + private var mYearAverageMode: Int? = null + var yearAverageMode: Int + get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES } + set(value) { config.set("yearAverageMode", value); mYearAverageMode = value } + + private var mHideImproved: Boolean? = null + var hideImproved: Boolean + get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false } + set(value) { config.set("hideImproved", value); mHideImproved = value } + + private var mAverageWithoutWeight: Boolean? = null + var averageWithoutWeight: Boolean + get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true } + set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value } + + private var mPlusValue: Float? = null + var plusValue: Float? + get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue } + set(value) { config.set("plusValue", value); mPlusValue = value } + private var mMinusValue: Float? = null + var minusValue: Float? + get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue } + set(value) { config.set("minusValue", value); mMinusValue = value } + + private var mDontCountEnabled: Boolean? = null + var dontCountEnabled: Boolean + get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false } + set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value } + + private var mDontCountGrades: List? = null + var dontCountGrades: List + get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() } + set(value) { config.set("dontCountGrades", value); mDontCountGrades = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt new file mode 100644 index 00000000..6d8f95e3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set + +class ProfileConfigSync(private val config: ProfileConfig) { + private var mNotificationFilter: List? = null + var notificationFilter: List + get() { mNotificationFilter = mNotificationFilter ?: config.values.get("notificationFilter", listOf()); return mNotificationFilter ?: listOf() } + set(value) { config.set("notificationFilter", value); mNotificationFilter = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt new file mode 100644 index 00000000..5ce237a0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-19. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel + +class ProfileConfigUI(private val config: ProfileConfig) { + private var mAgendaViewType: Int? = null + var agendaViewType: Int + get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } + set(value) { config.set("agendaViewType", value); mAgendaViewType = value } + + private var mHomeCards: List? = null + var homeCards: List + get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } + set(value) { config.set("homeCards", value); mHomeCards = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt new file mode 100644 index 00000000..f55df857 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +interface ConfigDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun add(entry: ConfigEntry) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addAll(list: List) + + @Query("SELECT * FROM config WHERE profileId = -1") + fun getAllNow(): List + + @Query("SELECT * FROM config WHERE profileId = :profileId") + fun getAllNow(profileId: Int): List + + @Query("DELETE FROM config WHERE profileId = :profileId") + fun clear(profileId: Int) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt new file mode 100644 index 00000000..6da441fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config.db + +import androidx.room.Entity + +@Entity(tableName = "config", primaryKeys = ["profileId", "key"]) +data class ConfigEntry( + val profileId: Int = -1, + val key: String, + val value: String? +) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt new file mode 100644 index 00000000..78a1d8a6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-19. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import android.content.SharedPreferences +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.math.abs + +class AppConfigMigrationV3(p: SharedPreferences, config: Config) { + init { config.apply { + val s = "app.appConfig" + if (dataVersion < 1) { + ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1 + sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true + sync.interval = p.getString("$s.registerSyncInterval", null)?.toIntOrNull() ?: 3600 + val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str -> + str.replace("[\\[\\]]*".toRegex(), "") + .split(",\\s?".toRegex()) + .mapNotNull { it.toIntOrNull() } + } + ui.miniMenuButtons = oldButtons ?: listOf( + MainActivity.DRAWER_ITEM_HOME, + MainActivity.DRAWER_ITEM_TIMETABLE, + MainActivity.DRAWER_ITEM_AGENDA, + MainActivity.DRAWER_ITEM_GRADES, + MainActivity.DRAWER_ITEM_MESSAGES, + MainActivity.DRAWER_ITEM_HOMEWORK, + MainActivity.DRAWER_ITEM_SETTINGS + ) + dataVersion = 1 + } + if (dataVersion < 2) { + devModePassword = p.getString("$s.devModePassword", null).fix() + sync.tokenApp = p.getString("$s.fcmToken", null).fix() + timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0 + appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0 + timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false + ui.headerBackground = p.getString("$s.headerBackground", null).fix() + ui.appBackground = p.getString("$s.appBackground", null).fix() + ui.language = p.getString("$s.language", null).fix() + appVersion = p.getString("$s.lastAppVersion", null)?.toIntOrNull() ?: BuildConfig.VERSION_CODE + appInstalledTime = p.getString("$s.appInstalledTime", null)?.toLongOrNull() ?: 0 + grades.orderBy = p.getString("$s.gradesOrderBy", null)?.toIntOrNull() ?: 0 + sync.quietDuringLessons = p.getString("$s.quietDuringLessons", null)?.toBoolean() ?: false + ui.miniMenuVisible = p.getString("$s.miniDrawerVisible", null)?.toBoolean() ?: false + loginFinished = p.getString("$s.loginFinished", null)?.toBoolean() ?: false + sync.onlyWifi = p.getString("$s.registerSyncOnlyWifi", null)?.toBoolean() ?: false + sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true + timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) } + + val startMillis = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0 + val endMillis = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0 + if (startMillis > 0) { + try { + sync.quietHoursStart = Time.fromMillis(abs(startMillis)) + sync.quietHoursEnd = Time.fromMillis(abs(endMillis)) + sync.quietHoursEnabled = true + } + catch (_: Exception) {} + } + else { + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + } + + sync.tokenMobidziennikList = listOf() + sync.tokenVulcanList = listOf() + sync.tokenLibrusList = listOf() + val tokens = p.getString("$s.fcmTokens", null)?.let { Gson().fromJson>>>(it, object: TypeToken>>>(){}.type) } + tokens?.forEach { + val token = it.value.first + when (it.key) { + LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token + LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token + LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token + } + } + dataVersion = 2 + } + }} + + private fun String?.fix(): String? { + return this?.replace("\"", "")?.let { if (it == "null") null else it } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt new file mode 100644 index 00000000..c98cf832 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import com.google.gson.* +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.config.AbstractConfig +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +private val gson = Gson() + +fun AbstractConfig.set(key: String, value: Int) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Boolean) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Long) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Float) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Date?) { + set(key, value?.stringY_m_d) +} +fun AbstractConfig.set(key: String, value: Time?) { + set(key, value?.stringValue) +} +fun AbstractConfig.set(key: String, value: JsonElement?) { + set(key, value?.toString()) +} +fun AbstractConfig.set(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.set(key: String, value: Any?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setStringList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setIntList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setLongList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setMap(key: String, value: Map?) { + set(key, value?.let { gson.toJson(it) }) +} + +fun HashMap.get(key: String, default: String?): String? { + return this[key] ?: default +} +fun HashMap.get(key: String, default: Boolean): Boolean { + return this[key]?.toBoolean() ?: default +} +fun HashMap.get(key: String, default: Int): Int { + return this[key]?.toIntOrNull() ?: default +} +fun HashMap.get(key: String, default: Long): Long { + return this[key]?.toLongOrNull() ?: default +} +fun HashMap.get(key: String, default: Float): Float { + return this[key]?.toFloatOrNull() ?: default +} +fun HashMap.get(key: String, default: Date?): Date? { + return this[key]?.let { Date.fromY_m_d(it) } ?: default +} +fun HashMap.get(key: String, default: Time?): Time? { + return this[key]?.let { Time.fromHms(it) } ?: default +} +fun HashMap.get(key: String, default: JsonObject?): JsonObject? { + return this[key]?.let { JsonParser().parse(it)?.asJsonObject } ?: default +} +fun HashMap.get(key: String, default: JsonArray?): JsonArray? { + return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default +} +inline fun HashMap.get(key: String, default: T?): T? { + return this[key]?.let { Gson().fromJson(it, T::class.java) } ?: default +} +/* !!! cannot use mutable list here - modifying it will not update the DB */ +fun HashMap.get(key: String, default: List?, classOfT: Class): List? { + return this[key]?.let { ConfigGsonUtils().deserializeList(gson, it, classOfT) } ?: default +} +fun HashMap.getStringList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} +fun HashMap.getIntList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} +fun HashMap.getLongList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} + +fun HashMap.getFloat(key: String): Float? { + return this[key]?.toFloatOrNull() +} + +fun List.toHashMap(profileId: Int, map: HashMap) { + map.clear() + forEach { + if (it.profileId == profileId) + map[it.key] = it.value + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt new file mode 100644 index 00000000..eb04578d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-2. + */ +package pl.szczodrzynski.edziennik.config.utils + +import com.google.gson.Gson +import com.google.gson.JsonParser +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigGsonUtils { + fun deserializeList(gson: Gson, str: String?, classOfT: Class): List { + val json = JsonParser().parse(str) + val list: MutableList = mutableListOf() + if (!json.isJsonArray) + return list + + json.asJsonArray.forEach { e -> + when (classOfT) { + String::class.java -> { + list += e.asString as T + } + HomeCardModel::class.java -> { + val o = e.asJsonObject + list += HomeCardModel( + o.getInt("profileId", 0), + o.getInt("cardId", 0) + ) as T + } + Time::class.java -> { + val o = e.asJsonObject + list += Time( + o.getInt("hour", 0), + o.getInt("minute", 0), + o.getInt("second", 0) + ) as T + } + } + } + + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt new file mode 100644 index 00000000..1095f511 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import android.content.Context +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.HOUR +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.math.abs + +class ConfigMigration(app: App, config: Config) { + init { config.apply { + + val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE) + if (p.contains("app.appConfig.appTheme")) { + // migrate appConfig from app version 3.x and lower. + // Updates dataVersion to level 2. + AppConfigMigrationV3(p, config) + } + + if (dataVersion < 2) { + appVersion = BuildConfig.VERSION_CODE + loginFinished = false + ui.language = null + ui.theme = 1 + ui.appBackground = null + ui.headerBackground = null + ui.miniMenuVisible = false + ui.miniMenuButtons = listOf( + MainActivity.DRAWER_ITEM_HOME, + MainActivity.DRAWER_ITEM_TIMETABLE, + MainActivity.DRAWER_ITEM_AGENDA, + MainActivity.DRAWER_ITEM_GRADES, + MainActivity.DRAWER_ITEM_MESSAGES, + MainActivity.DRAWER_ITEM_HOMEWORK, + MainActivity.DRAWER_ITEM_SETTINGS + ) + sync.enabled = true + sync.interval = 1*HOUR.toInt() + sync.notifyAboutUpdates = true + sync.onlyWifi = false + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + sync.quietDuringLessons = false + sync.tokenApp = null + sync.tokenMobidziennik = null + sync.tokenMobidziennikList = listOf() + sync.tokenLibrus = null + sync.tokenLibrusList = listOf() + sync.tokenVulcan = null + sync.tokenVulcanList = listOf() + timetable.bellSyncMultiplier = 0 + timetable.bellSyncDiff = null + timetable.countInSeconds = false + grades.orderBy = ORDER_BY_DATE_DESC + + dataVersion = 2 + } + + if (dataVersion < 3) { + update = null + privacyPolicyAccepted = false + debugMode = false + devModePassword = null + appInstalledTime = 0L + appRateSnackbarTime = 0L + + dataVersion = 3 + } + + if (dataVersion < 10) { + ui.openDrawerOnBackPressed = false + ui.snowfall = false + ui.bottomSheetOpened = false + sync.dontShowAppManagerDialog = false + sync.webPushEnabled = true + sync.lastAppSync = 0L + + + dataVersion = 10 + } + + if (dataVersion < 11) { + val startMillis = config.values.get("quietHoursStart", 0L) + val endMillis = config.values.get("quietHoursEnd", 0L) + if (startMillis > 0) { + try { + sync.quietHoursStart = Time.fromMillis(abs(startMillis)) + sync.quietHoursEnd = Time.fromMillis(abs(endMillis)) + sync.quietHoursEnabled = true + } + catch (_: Exception) {} + } + else { + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + } + + dataVersion = 11 + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt new file mode 100644 index 00000000..17f071f2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-1. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import pl.szczodrzynski.edziennik.config.ProfileConfig +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES + +class ProfileConfigMigration(config: ProfileConfig) { + init { config.apply { + + if (dataVersion < 1) { + grades.colorMode = COLOR_MODE_WEIGHTED + grades.yearAverageMode = YEAR_ALL_GRADES + grades.hideImproved = false + grades.averageWithoutWeight = true + grades.plusValue = null + grades.minusValue = null + grades.dontCountEnabled = false + grades.dontCountGrades = listOf() + ui.agendaViewType = AGENDA_DEFAULT + // no migration for ui.homeCards + + dataVersion = 1 + } + + if (dataVersion < 2) { + sync.notificationFilter = sync.notificationFilter + Notification.TYPE_TEACHER_ABSENCE + + dataVersion = 2 + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt index 38528a35..ad9c4296 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt @@ -2,7 +2,7 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-28. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api import android.app.Service import android.content.Context @@ -12,15 +12,17 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.events.* -import pl.szczodrzynski.edziennik.api.v2.events.requests.ServiceCloseRequest -import pl.szczodrzynski.edziennik.api.v2.events.requests.TaskCancelRequest -import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask -import pl.szczodrzynski.edziennik.api.v2.events.task.ErrorReportTask -import pl.szczodrzynski.edziennik.api.v2.events.task.IApiTask -import pl.szczodrzynski.edziennik.api.v2.events.task.NotifyTask -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.* +import pl.szczodrzynski.edziennik.data.api.events.requests.ServiceCloseRequest +import pl.szczodrzynski.edziennik.data.api.events.requests.TaskCancelRequest +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask +import pl.szczodrzynski.edziennik.data.api.task.IApiTask +import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.toApiError import pl.szczodrzynski.edziennik.utils.Utils.d import kotlin.math.min import kotlin.math.roundToInt @@ -28,7 +30,7 @@ import kotlin.math.roundToInt class ApiService : Service() { companion object { const val TAG = "ApiService" - const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.GET_DATA" + const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.SYNC" fun start(context: Context) { context.startService(Intent(context, ApiService::class.java)) } @@ -36,29 +38,34 @@ class ApiService : Service() { context.startService(Intent(context, ApiService::class.java)) EventBus.getDefault().postSticky(request) } + + var lastEventTime = System.currentTimeMillis() + var taskCancelTries = 0 } private val app by lazy { applicationContext as App } - private val finishingTaskQueue = mutableListOf( - NotifyTask(), - ErrorReportTask() - ) + private val syncingProfiles = mutableListOf() + + private var szkolnyTaskFinished = false + private val allTaskRequestList = mutableListOf() private val taskQueue = mutableListOf() private val errorList = mutableListOf() private var serviceClosed = false + set(value) { field = value; notification.serviceClosed = value } private var taskCancelled = false private var taskIsRunning = false private var taskRunning: IApiTask? = null // for debug purposes private var taskRunningId = -1 + private var taskStartTime = 0L private var taskMaximumId = 0 private var taskProfileId = -1 private var taskProgress = -1f private var taskProgressText: String? = null - private val notification by lazy { EdziennikNotification(this) } + private val notification by lazy { EdziennikNotification(app) } /* ______ _ _ _ _ _____ _ _ _ _ | ____| | | (_) (_) | / ____| | | | | | | @@ -68,33 +75,33 @@ class ApiService : Service() { |______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/ private val taskCallback = object : EdziennikCallback { override fun onCompleted() { - d(TAG, "Task $taskRunningId (profile $taskProfileId) - $taskProgressText - finished") - //if (!taskCancelled) { - EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId)) - //} - taskIsRunning = false - taskRunningId = -1 - taskRunning = null - taskProfileId = -1 - taskProgress = -1f - taskProgressText = null + lastEventTime = System.currentTimeMillis() + d(TAG, "Task $taskRunningId (profile $taskProfileId) finished in ${System.currentTimeMillis()-taskStartTime}") + EventBus.getDefault().postSticky(ApiTaskFinishedEvent(taskProfileId)) + clearTask() notification.setIdle().post() runTask() } override fun onError(apiError: ApiError) { + lastEventTime = System.currentTimeMillis() d(TAG, "Task $taskRunningId threw an error - $apiError") apiError.profileId = taskProfileId - EventBus.getDefault().post(ApiTaskErrorEvent(apiError)) - errorList.add(apiError) - apiError.throwable?.printStackTrace() + + if (app.userActionManager.requiresUserAction(apiError)) { + app.userActionManager.sendToUser(apiError) + } + else { + EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) + errorList.add(apiError) + apiError.throwable?.printStackTrace() + } + if (apiError.isCritical) { taskRunning?.cancel() notification.setCriticalError().post() - taskRunning = null - taskIsRunning = false - taskRunningId = -1 + clearTask() runTask() } else { @@ -103,6 +110,7 @@ class ApiService : Service() { } override fun onProgress(step: Float) { + lastEventTime = System.currentTimeMillis() if (step <= 0) return if (taskProgress < 0) @@ -115,6 +123,7 @@ class ApiService : Service() { } override fun onStartProgress(stringRes: Int) { + lastEventTime = System.currentTimeMillis() taskProgressText = getString(stringRes) d(TAG, "Task $taskRunningId progress: $taskProgressText") EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText)) @@ -129,15 +138,23 @@ class ApiService : Service() { | | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | | |_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/ private fun runTask() { + checkIfTaskFrozen() if (taskIsRunning) return - if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) { - serviceClosed = false + if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && szkolnyTaskFinished)) { allCompleted() return } - val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0) + lastEventTime = System.currentTimeMillis() + + val task = if (taskQueue.isNotEmpty()) { + taskQueue.removeAt(0) + } else { + szkolnyTaskFinished = true + SzkolnyTask(app, syncingProfiles) + } + task.taskId = ++taskMaximumId task.prepare(app) taskIsRunning = true @@ -147,7 +164,7 @@ class ApiService : Service() { taskProgress = -1f taskProgressText = task.taskName - d(TAG, "Executing task $taskRunningId ($taskProgressText) - $task") + d(TAG, "Executing task $taskRunningId - ${task::class.java.name}") // update the notification notification.setCurrentTask(taskRunningId, taskProgressText).post() @@ -155,19 +172,65 @@ class ApiService : Service() { // post an event EventBus.getDefault().post(ApiTaskStartedEvent(taskProfileId, task.profile)) + task.profile?.let { syncingProfiles.add(it) } + + taskStartTime = System.currentTimeMillis() try { when (task) { is EdziennikTask -> task.run(app, taskCallback) - is NotifyTask -> task.run(app, taskCallback) is ErrorReportTask -> task.run(app, taskCallback, notification, errorList) + is SzkolnyTask -> task.run(taskCallback) } } catch (e: Exception) { - taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e)) + taskCallback.onError(e.toApiError(TAG)) } } + /** + * Check if a task is inactive for more than 30 seconds. + * If the user tries to cancel a task with no success at least three times, + * consider it frozen as well. + * + * This usually means it is broken and won't become active again. + * This method cancels the task and removes any pointers to it. + */ + private fun checkIfTaskFrozen(): Boolean { + if (System.currentTimeMillis() - lastEventTime > 30*1000 + || taskCancelTries >= 3) { + val time = System.currentTimeMillis() - lastEventTime + d(TAG, "!!! Task $taskRunningId froze for $time ms. $taskRunning") + clearTask() + return true + } + return false + } + + /** + * Stops the service if the current task is frozen/broken. + */ + private fun stopIfTaskFrozen() { + if (checkIfTaskFrozen()) { + allCompleted() + } + } + + /** + * Remove any task descriptors or pointers from the service. + */ + private fun clearTask() { + taskIsRunning = false + taskRunningId = -1 + taskRunning = null + taskProfileId = -1 + taskProgress = -1f + taskProgressText = null + taskCancelled = false + taskCancelTries = 0 + } + private fun allCompleted() { - EventBus.getDefault().post(ApiTaskAllFinishedEvent()) + serviceClosed = true + EventBus.getDefault().postSticky(ApiTaskAllFinishedEvent()) stopSelf() } @@ -182,6 +245,13 @@ class ApiService : Service() { EventBus.getDefault().removeStickyEvent(task) d(TAG, task.toString()) + if (task is EdziennikTask) { + // fix for duplicated tasks, thank you EventBus + if (task.request in allTaskRequestList) + return + allTaskRequestList += task.request + } + if (task is EdziennikTask) { when (task.request) { is EdziennikTask.SyncRequest -> app.db.profileDao().idsForSyncNow.forEach { @@ -211,8 +281,10 @@ class ApiService : Service() { EventBus.getDefault().removeStickyEvent(request) d(TAG, request.toString()) + taskCancelTries++ taskCancelled = true taskRunning?.cancel() + stopIfTaskFrozen() } @Subscribe(sticky = true, threadMode = ThreadMode.ASYNC) fun onServiceCloseRequest(request: ServiceCloseRequest) { @@ -222,7 +294,7 @@ class ApiService : Service() { serviceClosed = true taskCancelled = true taskRunning?.cancel() - stopSelf() + allCompleted() } /* _____ _ _ _ @@ -239,11 +311,13 @@ class ApiService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { d(TAG, "Foreground service onStartCommand") - startForeground(EdziennikNotification.NOTIFICATION_ID, notification.notification) + startForeground(app.notificationChannelsManager.sync.id, notification.notification) return START_NOT_STICKY } override fun onDestroy() { + d(TAG, "Service destroyed") + serviceClosed = true EventBus.getDefault().unregister(this) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/AppError.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/AppError.java deleted file mode 100644 index 440bc0bb..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/AppError.java +++ /dev/null @@ -1,320 +0,0 @@ -package pl.szczodrzynski.edziennik.data.api; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Build; -import android.util.Log; - -import com.google.gson.JsonObject; - -import java.io.InterruptedIOException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLException; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import pl.szczodrzynski.edziennik.R; - - -public class AppError { - public static final int CODE_OTHER = 0; - public static final int CODE_OK = 1; - public static final int CODE_NO_INTERNET = 10; - public static final int CODE_SSL_ERROR = 13; - public static final int CODE_ARCHIVED = 5; - public static final int CODE_MAINTENANCE = 6; - public static final int CODE_LOGIN_ERROR = 7; - public static final int CODE_ACCOUNT_MISMATCH = 8; - public static final int CODE_APP_SERVER_ERROR = 9; - public static final int CODE_MULTIACCOUNT_SETUP = 12; - public static final int CODE_TIMEOUT = 11; - public static final int CODE_PROFILE_NOT_FOUND = 14; - public static final int CODE_ATTACHMENT_NOT_AVAILABLE = 28; - // user's fault - public static final int CODE_INVALID_LOGIN = 2; - public static final int CODE_INVALID_SERVER_ADDRESS = 21; - public static final int CODE_INVALID_SCHOOL_NAME = 22; - public static final int CODE_INVALID_DEVICE = 23; - public static final int CODE_OLD_PASSWORD = 4; - public static final int CODE_INVALID_TOKEN = 24; - public static final int CODE_EXPIRED_TOKEN = 27; - public static final int CODE_INVALID_SYMBOL = 25; - public static final int CODE_INVALID_PIN = 26; - public static final int CODE_LIBRUS_NOT_ACTIVATED = 29; - public static final int CODE_SYNERGIA_NOT_ACTIVATED = 32; - public static final int CODE_LIBRUS_DISCONNECTED = 31; - public static final int CODE_PROFILE_ARCHIVED = 30; - - - public static final int CODE_INTERNAL_MISSING_DATA = 100; - // internal errors - not for user's information. - // these error codes are processed in API main classes - - public String TAG; - public int line; - public int errorCode; - public String errorText; - public Response response; - public Request request; - public Throwable throwable; - public String apiResponse; - - public AppError(String TAG, int line, int errorCode, String errorText, Response response, Request request, Throwable throwable, String apiResponse) { - this.TAG = TAG; - this.line = line; - this.errorCode = errorCode; - this.errorText = errorText; - this.response = response; - this.request = request; - this.throwable = throwable; - this.apiResponse = apiResponse; - } - - public AppError(String TAG, int line, int errorCode) { - this(TAG, line, errorCode, null, null, null, null, null); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, null); - } - public AppError(String TAG, int line, int errorCode, Response response) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, null); - } - public AppError(String TAG, int line, int errorCode, Throwable throwable, String apiResponse) { - this(TAG, line, errorCode, null, null, null, throwable, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Throwable throwable, JsonObject apiResponse) { - this(TAG, line, errorCode, null, null, null, throwable, apiResponse == null ? null : apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText, Response response, JsonObject apiResponse) { - this(TAG, line, errorCode, errorText, response, response == null ? null : response.request(), null, apiResponse == null ? null : apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText, Response response, String apiResponse) { - this(TAG, line, errorCode, errorText, response, response == null ? null : response.request(), null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, String errorText, String apiResponse) { - this(TAG, line, errorCode, errorText, null, null, null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, String errorText, JsonObject apiResponse) { - this(TAG, line, errorCode, errorText, null, null, null, apiResponse == null ? null : apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText) { - this(TAG, line, errorCode, errorText, null, null, null, null); - } - public AppError(String TAG, int line, int errorCode, JsonObject apiResponse) { - this(TAG, line, errorCode, null, null, null, null, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable, JsonObject apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, apiResponse == null ? null : apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable, String apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Response response, String apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Response response, JsonObject apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, apiResponse == null ? null : apiResponse.toString()); - } - - public String getDetails(Context context) { - StringBuilder sb = new StringBuilder(); - sb.append(stringErrorCode(context, errorCode, errorText)).append("\n"); - sb.append("(").append(stringErrorType(errorCode)).append("#").append(errorCode).append(")\n"); - sb.append("at ").append(TAG).append(":").append(line).append("\n"); - sb.append("\n"); - if (throwable == null) - sb.append("Throwable is null"); - else - sb.append(Log.getStackTraceString(throwable)); - sb.append("\n"); - sb.append(Build.MANUFACTURER).append(" ").append(Build.BRAND).append(" ").append(Build.MODEL).append(" ").append(Build.DEVICE).append("\n"); - - return sb.toString(); - } - - public interface GetApiResponseCallback { - void onSuccess(String apiResponse); - } - /** - * - * @param context a Context - * @param apiResponseCallback a callback executed on a worker thread - */ - public void getApiResponse(Context context, GetApiResponseCallback apiResponseCallback) { - StringBuilder sb = new StringBuilder(); - sb.append("Request:\n"); - if (request != null) { - sb.append(request.method()).append(" ").append(request.url().toString()).append("\n"); - sb.append(request.headers().toString()).append("\n"); - sb.append("\n"); - sb.append(request.bodyToString()).append("\n\n"); - } - else - sb.append("null\n\n"); - - if (apiResponse == null && response != null) - apiResponse = response.parserErrorBody; - - sb.append("Response:\n"); - if (response != null) { - sb.append(response.code()).append(" ").append(response.message()).append("\n"); - sb.append(response.headers().toString()).append("\n"); - sb.append("\n"); - if (apiResponse == null) { - if (Thread.currentThread().getName().equals("main")) { - AsyncTask.execute(() -> { - if (response.raw().body() != null) { - try { - sb.append(response.raw().body().string()); - } catch (Exception e) { - sb.append("Exception while getting response body:\n").append(Log.getStackTraceString(e)); - } - } - else { - sb.append("null"); - } - apiResponseCallback.onSuccess(sb.toString()); - }); - } - else { - if (response.raw().body() != null) { - try { - sb.append(response.raw().body().string()); - } catch (Exception e) { - sb.append("Exception while getting response body:\n").append(Log.getStackTraceString(e)); - } - } - else { - sb.append("null"); - } - apiResponseCallback.onSuccess(sb.toString()); - } - return; - } - } - else - sb.append("null\n\n"); - - sb.append("API Response:\n"); - if (apiResponse != null) { - sb.append(apiResponse).append("\n\n"); - } - else { - sb.append("null\n\n"); - } - - apiResponseCallback.onSuccess(sb.toString()); - } - - public AppError changeIfCodeOther() { - if (errorCode != CODE_OTHER && errorCode != CODE_MAINTENANCE) - return this; - if (throwable instanceof UnknownHostException) - errorCode = CODE_NO_INTERNET; - else if (throwable instanceof SSLException) - errorCode = CODE_SSL_ERROR; - else if (throwable instanceof SocketTimeoutException) - errorCode = CODE_TIMEOUT; - else if (throwable instanceof InterruptedIOException) - errorCode = CODE_NO_INTERNET; - else if (response != null && - (response.code() == 424 - || response.code() == 400 - || response.code() == 401 - || response.code() == 500 - || response.code() == 503 - || response.code() == 404)) - errorCode = CODE_MAINTENANCE; - return this; - } - - public String asReadableString(Context context) { - return stringErrorCode(context, errorCode, errorText) + (errorCode == CODE_MAINTENANCE && errorText != null && !errorText.isEmpty() ? " ("+errorText+")" : ""); - } - - public static String stringErrorCode(Context context, int errorCode, String errorText) - { - switch (errorCode) { - case CODE_OK: - return context.getString(R.string.sync_error_ok); - case CODE_INVALID_LOGIN: - return context.getString(R.string.sync_error_invalid_login); - case CODE_LOGIN_ERROR: - return context.getString(R.string.sync_error_login_error); - case CODE_INVALID_DEVICE: - return context.getString(R.string.sync_error_invalid_device); - case CODE_OLD_PASSWORD: - return context.getString(R.string.sync_error_old_password); - case CODE_ARCHIVED: - return context.getString(R.string.sync_error_archived); - case CODE_MAINTENANCE: - return context.getString(R.string.sync_error_maintenance); - case CODE_NO_INTERNET: - return context.getString(R.string.sync_error_no_internet); - case CODE_ACCOUNT_MISMATCH: - return context.getString(R.string.sync_error_account_mismatch); - case CODE_APP_SERVER_ERROR: - return context.getString(R.string.sync_error_app_server); - case CODE_TIMEOUT: - return context.getString(R.string.sync_error_timeout); - case CODE_SSL_ERROR: - return context.getString(R.string.sync_error_ssl); - case CODE_INVALID_SERVER_ADDRESS: - return context.getString(R.string.sync_error_invalid_server_address); - case CODE_INVALID_SCHOOL_NAME: - return context.getString(R.string.sync_error_invalid_school_name); - case CODE_PROFILE_NOT_FOUND: - return context.getString(R.string.sync_error_profile_not_found); - case CODE_INVALID_TOKEN: - return context.getString(R.string.sync_error_invalid_token); - case CODE_ATTACHMENT_NOT_AVAILABLE: - return context.getString(R.string.sync_error_attachment_not_available); - case CODE_LIBRUS_NOT_ACTIVATED: - return context.getString(R.string.sync_error_librus_not_activated); - case CODE_PROFILE_ARCHIVED: - return context.getString(R.string.sync_error_profile_archived); - case CODE_LIBRUS_DISCONNECTED: - return context.getString(R.string.sync_error_librus_disconnected); - case CODE_SYNERGIA_NOT_ACTIVATED: - return context.getString(R.string.sync_error_synergia_not_activated); - default: - case CODE_MULTIACCOUNT_SETUP: - case CODE_OTHER: - return errorText != null ? errorText : context.getString(R.string.sync_error_unknown); - } - } - public static String stringErrorType(int errorCode) - { - switch (errorCode) { - default: - case CODE_OTHER: return "CODE_OTHER"; - case CODE_OK: return "CODE_OK"; - case CODE_NO_INTERNET: return "CODE_NO_INTERNET"; - case CODE_SSL_ERROR: return "CODE_SSL_ERROR"; - case CODE_ARCHIVED: return "CODE_ARCHIVED"; - case CODE_MAINTENANCE: return "CODE_MAINTENANCE"; - case CODE_LOGIN_ERROR: return "CODE_LOGIN_ERROR"; - case CODE_ACCOUNT_MISMATCH: return "CODE_ACCOUNT_MISMATCH"; - case CODE_APP_SERVER_ERROR: return "CODE_APP_SERVER_ERROR"; - case CODE_MULTIACCOUNT_SETUP: return "CODE_MULTIACCOUNT_SETUP"; - case CODE_TIMEOUT: return "CODE_TIMEOUT"; - case CODE_PROFILE_NOT_FOUND: return "CODE_PROFILE_NOT_FOUND"; - case CODE_INVALID_LOGIN: return "CODE_INVALID_LOGIN"; - case CODE_INVALID_SERVER_ADDRESS: return "CODE_INVALID_SERVER_ADDRESS"; - case CODE_INVALID_SCHOOL_NAME: return "CODE_INVALID_SCHOOL_NAME"; - case CODE_INVALID_DEVICE: return "CODE_INVALID_DEVICE"; - case CODE_OLD_PASSWORD: return "CODE_OLD_PASSWORD"; - case CODE_INVALID_TOKEN: return "CODE_INVALID_TOKEN"; - case CODE_EXPIRED_TOKEN: return "CODE_EXPIRED_TOKEN"; - case CODE_INVALID_SYMBOL: return "CODE_INVALID_SYMBOL"; - case CODE_INVALID_PIN: return "CODE_INVALID_PIN"; - case CODE_ATTACHMENT_NOT_AVAILABLE: return "CODE_ATTACHMENT_NOT_AVAILABLE"; - case CODE_LIBRUS_NOT_ACTIVATED: return "CODE_LIBRUS_NOT_ACTIVATED"; - case CODE_PROFILE_ARCHIVED: return "CODE_PROFILE_ARCHIVED"; - case CODE_LIBRUS_DISCONNECTED: return "CODE_LIBRUS_DISCONNECTED"; - case CODE_SYNERGIA_NOT_ACTIVATED: return "CODE_SYNERGIA_NOT_ACTIVATED"; - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt similarity index 67% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index ef594568..af006d5a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -2,7 +2,7 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-19. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api import android.os.Build import pl.szczodrzynski.edziennik.BuildConfig @@ -14,24 +14,24 @@ val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Androi val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT" -const val FAKE_LIBRUS_API = "http://librus.szkolny.eu/api" -const val FAKE_LIBRUS_PORTAL = "http://librus.szkolny.eu" -const val FAKE_LIBRUS_AUTHORIZE = "http://librus.szkolny.eu/authorize.php" -const val FAKE_LIBRUS_LOGIN = "http://librus.szkolny.eu/login_action.php" -const val FAKE_LIBRUS_TOKEN = "http://librus.szkolny.eu/access_token.php" +const val FAKE_LIBRUS_API = "https://librus.szkolny.eu/api" +const val FAKE_LIBRUS_PORTAL = "https://librus.szkolny.eu" +const val FAKE_LIBRUS_AUTHORIZE = "https://librus.szkolny.eu/authorize.php" +const val FAKE_LIBRUS_LOGIN = "https://librus.szkolny.eu/login_action.php" +const val FAKE_LIBRUS_TOKEN = "https://librus.szkolny.eu/access_token.php" const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login=" const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php" -val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp" +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 = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv" -const val LIBRUS_REDIRECT_URL = "http://localhost/bar" +const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc" +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" const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token" -const val LIBRUS_ACCOUNT_URL = "/v2/SynergiaAccounts/fresh/" // + login -const val LIBRUS_ACCOUNTS_URL = "/v2/SynergiaAccounts" +const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login +const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts" /** https://api.librus.pl/2.0 */ const val LIBRUS_API_URL = "https://api.librus.pl/2.0" @@ -56,18 +56,29 @@ const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/to const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module" const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" +const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile" +const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik" + const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik" const val IDZIENNIK_WEB_LOGIN = "login.aspx" const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx" +const val IDZIENNIK_WEB_HOME = "mod_panelRodzica/StronaGlowna.aspx" const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec" const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia" const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia" const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe" +const val IDZIENNIK_WEB_HOMEWORK = "mod_panelRodzica/pracaDomowa/WS_pracaDomowa.asmx/pobierzPraceDomowe" const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia" const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia" const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia" const val IDZIENNIK_WEB_MESSAGES_LIST = "mod_komunikator/WS_wiadomosci.asmx/PobierzListeWiadomosci" +const val IDZIENNIK_WEB_GET_MESSAGE = "mod_komunikator/WS_wiadomosci.asmx/PobierzWiadomosc" +const val IDZIENNIK_WEB_GET_RECIPIENT_LIST = "mod_komunikator/WS_wiadomosci.asmx/pobierzListeOdbiorcowPanelRodzic" +const val IDZIENNIK_WEB_SEND_MESSAGE = "mod_komunikator/WS_wiadomosci.asmx/WyslijWiadomosc" +const val IDZIENNIK_WEB_GET_ATTACHMENT = "mod_komunikator/Download.ashx" +const val IDZIENNIK_WEB_GET_HOMEWORK = "mod_panelRodzica/pracaDomowa/WS_pracaDomowa.asmx/pobierzJednaPraceDomowa" +const val IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT = "mod_panelRodzica/pracaDomowa.aspx" val IDZIENNIK_API_USER_AGENT = SYSTEM_USER_AGENT const val IDZIENNIK_API_URL = "https://iuczniowie.progman.pl/idziennik/api" @@ -81,7 +92,7 @@ val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT const val VULCAN_API_USER_AGENT = "MobileUserAgent" const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia" -const val VULCAN_API_APP_VERSION = "19.4.1.436" +const val VULCAN_API_APP_VERSION = "20.5.1.470" const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06" const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB" val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}" @@ -99,4 +110,15 @@ const val VULCAN_API_ENDPOINT_ATTENDANCE = "mobile-api/Uczen.v3.Uczen/Frekwencje const val VULCAN_API_ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/WiadomosciOdebrane" const val VULCAN_API_ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane" const val VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS = "mobile-api/Uczen.v3.Uczen/ZmienStatusWiadomosci" +const val VULCAN_API_ENDPOINT_MESSAGES_ADD = "mobile-api/Uczen.v3.Uczen/DodajWiadomosc" const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken" +const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/WiadomosciZalacznik" +const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik" +const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers" +const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get" + +const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}" + +const val PODLASIE_API_VERSION = "1.0.31" +const val PODLASIE_API_URL = "https://cpdklaser.zeto.bialystok.pl/api" +const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EdziennikNotification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt similarity index 65% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EdziennikNotification.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt index 2db9ecf1..605b644c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EdziennikNotification.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt @@ -2,28 +2,26 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-1. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.content.Context -import android.content.Intent import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_MIN +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.Bundle import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.receivers.SzkolnyReceiver import kotlin.math.roundToInt -class EdziennikNotification(val context: Context) { - companion object { - const val NOTIFICATION_ID = 20191001 - } - - private val notificationManager by lazy { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } +class EdziennikNotification(val app: App) { + private val notificationManager by lazy { app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } private val notificationBuilder: NotificationCompat.Builder by lazy { - NotificationCompat.Builder(context, ApiService.NOTIFICATION_API_CHANNEL_ID) + NotificationCompat.Builder(app, ApiService.NOTIFICATION_API_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setPriority(PRIORITY_MIN) .setOngoing(true) @@ -35,39 +33,42 @@ class EdziennikNotification(val context: Context) { private var errorCount = 0 private var criticalErrorCount = 0 + var serviceClosed = false private fun cancelPendingIntent(taskId: Int): PendingIntent { - val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN") - intent.putExtra("task", "TaskCancelRequest") - intent.putExtra("taskId", taskId) - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent + val intent = SzkolnyReceiver.getIntent(app, Bundle( + "task" to "TaskCancelRequest", + "taskId" to taskId + )) + return PendingIntent.getBroadcast(app, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) as PendingIntent } private val closePendingIntent: PendingIntent get() { - val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN") - intent.putExtra("task", "ServiceCloseRequest") - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent + val intent = SzkolnyReceiver.getIntent(app, Bundle( + "task" to "ServiceCloseRequest" + )) + return PendingIntent.getBroadcast(app, 0, intent, 0) as PendingIntent } private fun errorCountText(): String? { var result = "" if (criticalErrorCount > 0) { - result += context.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount) + result += app.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount) } if (criticalErrorCount > 0 && errorCount > 0) { result += ", " } if (errorCount > 0) { - result += context.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount) + result += app.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount) } return if (result.isEmpty()) null else result } fun setIdle(): EdziennikNotification { - notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_title)) + notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_title)) notificationBuilder.setProgress(0, 0, false) notificationBuilder.apply { - val str = context.getString(R.string.edziennik_notification_api_text) + val str = app.getString(R.string.edziennik_notification_api_text) setStyle(NotificationCompat.BigTextStyle().bigText(str)) setContentText(str) } @@ -81,7 +82,7 @@ class EdziennikNotification(val context: Context) { } fun setCriticalError(): EdziennikNotification { criticalErrorCount++ - notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_error_title)) + notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_error_title)) notificationBuilder.setProgress(0, 0, false) notificationBuilder.apply { val str = errorCountText() @@ -118,7 +119,7 @@ class EdziennikNotification(val context: Context) { notificationBuilder.addAction( NotificationCompat.Action( R.drawable.ic_notification, - context.getString(R.string.edziennik_notification_api_close), + app.getString(R.string.edziennik_notification_api_close), closePendingIntent )) return this @@ -128,13 +129,15 @@ class EdziennikNotification(val context: Context) { notificationBuilder.addAction( NotificationCompat.Action( R.drawable.ic_notification, - context.getString(R.string.edziennik_notification_api_cancel), + app.getString(R.string.edziennik_notification_api_cancel), cancelPendingIntent(taskId) )) } fun post() { - notificationManager.notify(NOTIFICATION_ID, notification) + if (serviceClosed) + return + notificationManager.notify(app.notificationChannelsManager.sync.id, notification) } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EndpointChooser.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt similarity index 55% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EndpointChooser.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt index c4a97f98..7f882123 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/EndpointChooser.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt @@ -1,13 +1,13 @@ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.models.Data -import pl.szczodrzynski.edziennik.api.v2.models.Feature -import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod -import pl.szczodrzynski.edziennik.data.db.modules.api.EndpointTimer -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_NEVER +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.api.models.LoginMethod +import pl.szczodrzynski.edziennik.data.db.entity.EndpointTimer +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_NEVER -fun Data.prepare(loginMethods: List, features: List, featureIds: List, viewId: Int?) { +fun Data.prepare(loginMethods: List, features: List, featureIds: List, viewId: Int?, onlyEndpoints: List?) { val data = this val possibleLoginMethods = data.loginMethods.toMutableList() @@ -46,13 +46,19 @@ fun Data.prepare(loginMethods: List, features: List, featu // add all endpoint IDs and required login methods, filtering using timers .onEach { feature -> feature.endpointIds.forEach { endpoint -> + if (onlyEndpoints?.contains(endpoint.first) == false) + return@forEach (data.endpointTimers - .singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id ?: -1, endpoint.first)) + .singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id + ?: -1, endpoint.first)) .let { timer -> - if (timer.nextSync == SYNC_ALWAYS || - (viewId != null && timer.viewId == viewId) || - (timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp)) { - data.targetEndpointIds.add(endpoint.first) + if ( + onlyEndpoints?.contains(endpoint.first) == true || + timer.nextSync == SYNC_ALWAYS || + viewId != null && timer.viewId == viewId || + timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp + ) { + data.targetEndpointIds[endpoint.first] = timer.lastSync requiredLoginMethods.add(endpoint.second) } } @@ -75,9 +81,37 @@ fun Data.prepare(loginMethods: List, features: List, featu data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList() data.targetLoginMethodIds.sort() - data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList() - data.targetEndpointIds.sort() + //data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList() + //data.targetEndpointIds.sort() progressCount = targetLoginMethodIds.size + targetEndpointIds.size progressStep = if (progressCount <= 0) 0f else 100f / progressCount.toFloat() -} \ No newline at end of file +} + +fun Data.prepareFor(loginMethods: List, loginMethodId: Int) { + val possibleLoginMethods = this.loginMethods.toMutableList() + + loginMethods.forEach { + if (it.isPossible(profile, loginStore)) + possibleLoginMethods += it.loginMethodId + } + + targetLoginMethodIds.clear() + + // check the login method for any dependencies + var requiredLoginMethod: Int? = loginMethodId + while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { + loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { + if (requiredLoginMethod != null) + targetLoginMethodIds.add(requiredLoginMethod!!) + requiredLoginMethod = it.requiredLoginMethod(profile, loginStore) + } + } + + // sort and distinct every login method + targetLoginMethodIds = targetLoginMethodIds.toHashSet().toMutableList() + targetLoginMethodIds.sort() + + progressCount = 0 + progressStep = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt similarity index 73% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 0704d2a0..8301f62f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -2,7 +2,7 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-21. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api /*const val CODE_OTHER = 0 const val CODE_OK = 1 @@ -31,6 +31,11 @@ const val CODE_SYNERGIA_NOT_ACTIVATED = 32 const val CODE_LIBRUS_DISCONNECTED = 31 const val CODE_PROFILE_ARCHIVED = 30*/ +const val ERROR_APP_CRASH = 1 +const val ERROR_EXCEPTION = 2 +const val ERROR_API_EXCEPTION = 3 +const val ERROR_MESSAGE_NOT_SENT = 10 + const val ERROR_REQUEST_FAILURE = 50 const val ERROR_REQUEST_HTTP_400 = 51 const val ERROR_REQUEST_HTTP_401 = 52 @@ -38,16 +43,27 @@ const val ERROR_REQUEST_HTTP_403 = 53 const val ERROR_REQUEST_HTTP_404 = 54 const val ERROR_REQUEST_HTTP_405 = 55 const val ERROR_REQUEST_HTTP_410 = 56 -const val ERROR_REQUEST_HTTP_500 = 57 +const val ERROR_REQUEST_HTTP_424 = 57 +const val ERROR_REQUEST_HTTP_500 = 58 +const val ERROR_REQUEST_HTTP_503 = 59 +const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60 +const val ERROR_REQUEST_FAILURE_TIMEOUT = 61 +const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62 +const val ERROR_REQUEST_FAILURE_SSL_ERROR = 63 const val ERROR_RESPONSE_EMPTY = 100 const val ERROR_LOGIN_DATA_MISSING = 101 const val ERROR_PROFILE_MISSING = 105 +const val ERROR_PROFILE_ARCHIVED = 106 const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_NOT_IMPLEMENTED = 112 +const val ERROR_FILE_DOWNLOAD = 113 const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115 +const val ERROR_CAPTCHA_NEEDED = 3000 +const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001 + const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120 const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121 const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124 @@ -104,6 +120,14 @@ const val ERROR_LIBRUS_MESSAGES_OTHER = 178 const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179 const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180 const val ERROR_LIBRUS_API_MAINTENANCE = 181 +const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182 +const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183 +const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184 +const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185 +const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186 +const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187 +const val ERROR_LIBRUS_MESSAGES_ATTACHMENT_NOT_FOUND = 188 +const val ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT = 189 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 @@ -118,6 +142,9 @@ const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216 const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213 const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215 +const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216 +const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217 +const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218 const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301 const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302 @@ -132,6 +159,17 @@ const val ERROR_LOGIN_VULCAN_NO_PUPILS = 331 const val ERROR_VULCAN_API_MAINTENANCE = 340 const val ERROR_VULCAN_API_BAD_REQUEST = 341 const val ERROR_VULCAN_API_OTHER = 342 +const val ERROR_VULCAN_ATTACHMENT_DOWNLOAD = 343 +const val ERROR_VULCAN_WEB_DATA_MISSING = 344 +const val ERROR_VULCAN_WEB_429 = 345 +const val ERROR_VULCAN_WEB_OTHER = 346 +const val ERROR_VULCAN_WEB_NO_CERTIFICATE = 347 +const val ERROR_VULCAN_WEB_NO_REGISTER = 348 +const val ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED = 349 +const val ERROR_VULCAN_WEB_LOGGED_OUT = 350 +const val ERROR_VULCAN_WEB_CERTIFICATE_POST_FAILED = 351 +const val ERROR_VULCAN_WEB_GRADUATE_ACCOUNT = 352 +const val ERROR_VULCAN_WEB_NO_SCHOOLS = 353 const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401 const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402 @@ -152,6 +190,21 @@ const val ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR = 440 const val ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA = 441 const val ERROR_IDZIENNIK_API_ACCESS_DENIED = 450 const val ERROR_IDZIENNIK_API_OTHER = 451 +const val ERROR_IDZIENNIK_API_NO_REGISTER = 452 +const val ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION = 453 + +const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501 +const val ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER = 510 +const val ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID = 511 +const val ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS = 521 +const val ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED = 522 +const val ERROR_EDUDZIENNIK_WEB_TEAM_MISSING = 530 + +const val ERROR_LOGIN_PODLASIE_API_INVALID_TOKEN = 601 +const val ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT = 602 +const val ERROR_PODLASIE_API_NO_TOKEN = 630 +const val ERROR_PODLASIE_API_OTHER = 631 +const val ERROR_PODLASIE_API_DATA_MISSING = 632 const val ERROR_TEMPLATE_WEB_OTHER = 801 @@ -163,10 +216,18 @@ const val EXCEPTION_LIBRUS_API_REQUEST = 904 const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905 const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906 const val EXCEPTION_VULCAN_API_REQUEST = 907 -const val EXCEPTION_NOTIFY_AND_SYNC = 910 +const val EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST = 908 +const val EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST = 909 +const val EXCEPTION_NOTIFY = 910 const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911 const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912 const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913 const val EXCEPTION_IDZIENNIK_API_REQUEST = 914 +const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920 +const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921 +const val ERROR_ONEDRIVE_DOWNLOAD = 930 +const val EXCEPTION_VULCAN_WEB_LOGIN = 931 +const val EXCEPTION_VULCAN_WEB_REQUEST = 932 +const val EXCEPTION_PODLASIE_API_REQUEST = 940 const val LOGIN_NO_ARGUMENTS = 1201 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Features.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt similarity index 93% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Features.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt index 78b77f07..e38b37b5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Features.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt @@ -2,7 +2,7 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-29. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS @@ -13,8 +13,8 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT internal const val FEATURE_TIMETABLE = 1 internal const val FEATURE_AGENDA = 2 @@ -41,6 +41,7 @@ internal const val FEATURE_PUSH_CONFIG = 120 object Features { private fun getAllNecessary(): List = listOf( FEATURE_ALWAYS_NEEDED, + FEATURE_PUSH_CONFIG, FEATURE_STUDENT_INFO, FEATURE_STUDENT_NUMBER, FEATURE_SCHOOL_INFO, @@ -82,4 +83,4 @@ object Features { else -> getAllFeatures() } + getAllNecessary()).sorted() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt similarity index 66% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt index 1d4b6288..24444a58 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt @@ -2,19 +2,23 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-20. */ -package pl.szczodrzynski.edziennik.api.v2 +package pl.szczodrzynski.edziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginApi -import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb -import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod -import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginApi -import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginWeb -import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginMessages +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginApi2 +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain +import pl.szczodrzynski.edziennik.data.api.models.LoginMethod // librus // mobidziennik @@ -22,8 +26,7 @@ import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi // vulcan // mobireg -const val SYNERGIA_API_ENABLED = true - +const val SYNERGIA_API_ENABLED = false const val LOGIN_TYPE_IDZIENNIK = 3 @@ -68,13 +71,13 @@ val librusLoginMethods = listOf( LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withRequiredLoginMethod { profile, _ -> - if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED + if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED }, LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withRequiredLoginMethod { profile, _ -> - if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED + if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED } ) @@ -85,11 +88,11 @@ const val LOGIN_METHOD_MOBIDZIENNIK_API2 = 300 val mobidziennikLoginMethods = listOf( LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_WEB, MobidziennikLoginWeb::class.java) .withIsPossible { _, _ -> true } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }/*, + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_API2, MobidziennikLoginApi2::class.java) - .withIsPossible { _, loginStore -> loginStore.hasLoginData("email") } - .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }*/ + .withIsPossible { profile, _ -> profile?.getStudentData("email", null) != null } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } ) const val LOGIN_TYPE_VULCAN = 4 @@ -101,11 +104,11 @@ const val LOGIN_METHOD_VULCAN_WEB_OLD = 300 const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400 const val LOGIN_METHOD_VULCAN_API = 500 val vulcanLoginMethods = listOf( - /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java) - .withIsPossible { _, _ -> false } + LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java) + .withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") } .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, - LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java) + /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java) .withIsPossible { _, _ -> false } .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN }, @@ -116,7 +119,7 @@ val vulcanLoginMethods = listOf( LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java) .withIsPossible { _, _ -> true } .withRequiredLoginMethod { _, loginStore -> - if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED + if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED } ) @@ -130,6 +133,24 @@ val idziennikLoginMethods = listOf( .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB } ) +const val LOGIN_TYPE_EDUDZIENNIK = 5 +const val LOGIN_MODE_EDUDZIENNIK_WEB = 0 +const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100 +val edudziennikLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_EDUDZIENNIK, LOGIN_METHOD_EDUDZIENNIK_WEB, EdudziennikLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } +) + +const val LOGIN_TYPE_PODLASIE = 6 +const val LOGIN_MODE_PODLASIE_API = 0 +const val LOGIN_METHOD_PODLASIE_API = 100 +val podlasieLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_PODLASIE, LOGIN_METHOD_PODLASIE_API, PodlasieLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } +) + val templateLoginMethods = listOf( LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java) .withIsPossible { _, _ -> true } @@ -138,4 +159,4 @@ val templateLoginMethods = listOf( LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java) .withIsPossible { _, _ -> true } .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB } -) \ No newline at end of file +) 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 new file mode 100644 index 00000000..894dfe7a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -0,0 +1,259 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-6. + */ + +package pl.szczodrzynski.edziennik.data.api + +import kotlin.text.RegexOption.DOT_MATCHES_ALL +import kotlin.text.RegexOption.IGNORE_CASE + +object Regexes { + val STYLE_CSS_COLOR by lazy { + """color: (\w+);?""".toRegex() + } + + + + val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy { + """\n*\s*(.+?)\s*\n*(?:<.*?)??""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_COLOR by lazy { + """background-color:([#A-Fa-f0-9]+);""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_CATEGORY by lazy { + """> (.+?):
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy { + """Średnia ocen:.*([0-9]*\.?[0-9]*)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy { + """Wpisano:.*.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy { + """Liczona do średniej:.*?nie
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_DETAILS by lazy { + """(.+?).*?.+?.*?(?:\((.+?)\).*?)?.*?Wartość oceny:.*?([0-9.]+).*?Wpisał\(a\):.*?(.+?).*?(?:Komentarz:.*?(.+?))?""".toRegex(DOT_MATCHES_ALL) + } + + val MOBIDZIENNIK_EVENT_TYPE by lazy { + """\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_LUCKY_NUMBER by lazy { + """class="szczesliwy_numerek".*?>0?([0-9]+)/?0?([0-9]+)?""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_CLASS_CALENDAR by lazy { + """events: (.+),$""".toRegex(RegexOption.MULTILINE) + } + + val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy { + """czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy { + """.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy { + """href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?(.+?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_TABLE by lazy { + """(.+?)
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_LESSON_COUNT by lazy { + """rel="([0-9-]{10})" colspan="([0-9]+)"""".toRegex() + } + val MOBIDZIENNIK_ATTENDANCE_ENTRIES by lazy { + """font-size:.+?class=".*?">(.*?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_COLUMNS by lazy { + """(.+?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_COLUMN by lazy { + """()(.*?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_COLUMN_SPAN by lazy { + """colspan="(\d+)"""".toRegex() + } + val MOBIDZIENNIK_ATTENDANCE_RANGE by lazy { + """([0-9:]+) - .+? (.+?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_LESSON by lazy { + """(.+?)\s*\s*\((.+?),\s*(.+?)\)""".toRegex(DOT_MATCHES_ALL) + } + + val MOBIDZIENNIK_HOMEWORK_ROW by lazy { + """class="rowRolling">(.+?\s*)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_HOMEWORK_ITEM by lazy { + """

(.+?):\s*(.+?)\s*

""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_HOMEWORK_BODY by lazy { + """Treść:(.+?)

""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_HOMEWORK_ID by lazy { + """zadanieFormularz\(([0-9]+),""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_HOMEWORK_ATTACHMENT by lazy { + """zalacznik(_zadania)?=([0-9]+)'.+?word-break">(.+?)""".toRegex(DOT_MATCHES_ALL) + } + + + + val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { + """""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_ERROR by lazy { + """id="spanErrorMessage">(.*?)(.+?)""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { + """id="ctl00_CzyRodzic" value="([01])" />""".toRegex() + } + val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { + """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9]+)/([0-9]+)<""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { + """""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { + """(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_MESSAGES_RECIPIENT_PARENT by lazy { + """(.+?)\s\((.+)\)""".toRegex() + } + /*Szczęśliwy los na dzisiaj to 19. Los na jutro to 22*/ + val IDZIENNIK_WEB_LUCKY_NUMBER by lazy { + """dzisiaj to ([0-9]+)""".toRegex() + } + val IDZIENNIK_WEB_SELECTED_REGISTER by lazy { + """selected="selected" value="([0-9]+)" data-id-ucznia""".toRegex() + } + + + + val VULCAN_SHIFT_ANNOTATION by lazy { + """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() + } + val VULCAN_WEB_PERMISSIONS by lazy { + """permissions: '([A-z0-9/=+\-_]+?)'""".toRegex() + } + val VULCAN_WEB_SYMBOL_VALIDATE by lazy { + """[A-z0-9]+""".toRegex(IGNORE_CASE) + } + + + + val LIBRUS_ATTACHMENT_KEY by lazy { + """singleUseKey=([0-9A-z_]+)""".toRegex() + } + val LIBRUS_MESSAGE_ID by lazy { + """/wiadomosci/[0-9]+/[0-9]+/([0-9]+?)/""".toRegex() + } + + + + val EDUDZIENNIK_STUDENTS_START by lazy { + """

  • (.*?)""".toRegex() + } + val EDUDZIENNIK_ACCOUNT_NAME_START by lazy { + """(.*?)""".toRegex() + } + val EDUDZIENNIK_SUBJECTS_START by lazy { + """(.+?)""".toRegex() + } + + val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy { + """(.+?)""".toRegex() + } + val EDUDZIENNIK_ATTENDANCE_TYPES by lazy { + """
    .*?

    (.*?)

    """.toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_ATTENDANCE_TYPE by lazy { + """\((.+?)\) (.+)""".toRegex() + } + + val EDUDZIENNIK_ANNOUNCEMENT_DESCRIPTION by lazy { + """
    .*?

    (.*?)

    """.toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_HOMEWORK_DESCRIPTION by lazy { + """
    (.*?)
    """.toRegex(DOT_MATCHES_ALL) + } + + val EDUDZIENNIK_SUBJECT_ID by lazy { + """/Courses/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_GRADE_ID by lazy { + """/Grades/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EXAM_ID by lazy { + """/Evaluations/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EVENT_TYPE_ID by lazy { + """/GradeLabels/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_ANNOUNCEMENT_ID by lazy { + """/Announcement/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_HOMEWORK_ID by lazy { + """/Homework/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_TEACHER_ID by lazy { + """/Teachers/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EVENT_ID by lazy { + """/KlassEvent/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_NOTE_ID by lazy { + """/RegistryNotes/([\w-_]+?)/""".toRegex() + } + + val EDUDZIENNIK_SCHOOL_DETAIL_ID by lazy { + """.*?

    (.*?)

    .*?
  • """.toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_CLASS_DETAIL_ID by lazy { + """(.*?)""".toRegex(DOT_MATCHES_ALL) + } + + val EDUDZIENNIK_TEACHERS by lazy { + """
    .*?

    (.+?) (.+?)

    """.toRegex(DOT_MATCHES_ALL) + } + + + + + val LINKIFY_DATE_YMD by lazy { + """(1\d{3}|20\d{2})[\-./](1[0-2]|0?\d)[\-./]([1-2]\d|3[0-1]|0?\d)""".toRegex() + } + val LINKIFY_DATE_DMY by lazy { + """(?>? = null, onlyEndpoints: List? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, onlyEndpoints, arguments)) + fun syncProfileList(profileList: List) = EdziennikTask(-1, SyncProfileListRequest(profileList)) + fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message)) + fun messageSend(profileId: Int, recipients: List, subject: String, text: String) = EdziennikTask(profileId, MessageSendRequest(recipients, subject, text)) + fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) + fun announcementGet(profileId: Int, announcement: AnnouncementFull) = EdziennikTask(profileId, AnnouncementGetRequest(announcement)) + fun attachmentGet(profileId: Int, owner: Any, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(owner, attachmentId, attachmentName)) + fun recipientListGet(profileId: Int) = EdziennikTask(profileId, RecipientListGetRequest()) + fun eventGet(profileId: Int, event: EventFull) = EdziennikTask(profileId, EventGetRequest(event)) + } + + private lateinit var loginStore: LoginStore + + override fun prepare(app: App) { + if (request is FirstLoginRequest) { + // get the requested profile and login store + this.profile = null + loginStore = request.loginStore + // save the profile ID and name as the current task's + taskName = app.getString(R.string.edziennik_notification_api_first_login_title) + } else { + // get the requested profile and login store + val profile = app.db.profileDao().getByIdNow(profileId) ?: return + this.profile = profile + val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return + this.loginStore = loginStore + // save the profile ID and name as the current task's + taskName = app.getString(R.string.edziennik_notification_api_sync_title_format, profile.name) + } + EdziennikTask.profile = this.profile + EdziennikTask.loginStore = this.loginStore + } + + private var edziennikInterface: EdziennikInterface? = null + + internal fun run(app: App, taskCallback: EdziennikCallback) { + profile?.let { profile -> + if (profile.archived) { + d(TAG, "The profile $profileId is archived") + taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED)) + return + } + else if (profile.shouldArchive()) { + d(TAG, "The profile $profileId's year ended on ${profile.dateYearEnd}, archiving") + ProfileArchiver(app, profile) + } + if (profile.isBeforeYear()) { + d(TAG, "The profile $profileId's school year has not started yet; aborting sync") + cancel() + taskCallback.onCompleted() + return + } + + profile.registerName?.let { registerName -> + var status = app.config.sync.registerAvailability[registerName] + if (status == null || status.nextCheck < currentTimeUnix()) { + val api = SzkolnyApi(app) + api.runCatching({ + val availability = getRegisterAvailability() + app.config.sync.registerAvailability = availability + status = availability[registerName] + }, onError = { + taskCallback.onError(it.toApiError(TAG)) + return + }) + } + + if (status?.available != true + || status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) { + if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { + EventBus.getDefault().postSticky( + RegisterAvailabilityEvent(app.config.sync.registerAvailability) + ) + } + cancel() + taskCallback.onCompleted() + return + } + } + } + + edziennikInterface = when (loginStore.type) { + LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) + LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) + LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_EDUDZIENNIK -> Edudziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_PODLASIE -> Podlasie(app, profile, loginStore, taskCallback) + LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) + else -> null + } + if (edziennikInterface == null) { + return + } + + when (request) { + is SyncProfileRequest -> edziennikInterface?.sync( + featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } + ?: Features.getAllIds(), + viewId = request.viewIds?.get(0)?.first, + onlyEndpoints = request.onlyEndpoints, + arguments = request.arguments) + is MessageGetRequest -> edziennikInterface?.getMessage(request.message) + is MessageSendRequest -> edziennikInterface?.sendMessage(request.recipients, request.subject, request.text) + is FirstLoginRequest -> edziennikInterface?.firstLogin() + is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead() + is AnnouncementGetRequest -> edziennikInterface?.getAnnouncement(request.announcement) + is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.owner, request.attachmentId, request.attachmentName) + is RecipientListGetRequest -> edziennikInterface?.getRecipientList() + is EventGetRequest -> edziennikInterface?.getEvent(request.event) + } + } + + override fun cancel() { + d(TAG, "Task ${toString()} cancelling...") + edziennikInterface?.cancel() + } + + override fun toString(): String { + return "EdziennikTask(profileId=$profileId, request=$request, edziennikInterface=$edziennikInterface)" + } + + data class FirstLoginRequest(val loginStore: LoginStore) + class SyncRequest + data class SyncProfileRequest(val viewIds: List>? = null, val onlyEndpoints: List? = null, val arguments: JsonObject? = null) + data class SyncProfileListRequest(val profileList: List) + data class MessageGetRequest(val message: MessageFull) + data class MessageSendRequest(val recipients: List, val subject: String, val text: String) + class AnnouncementsReadRequest + data class AnnouncementGetRequest(val announcement: AnnouncementFull) + data class AttachmentGetRequest(val owner: Any, val attachmentId: Long, val attachmentName: String) + class RecipientListGetRequest + data class EventGetRequest(val event: EventFull) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt new file mode 100644 index 00000000..15da9765 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-8-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik + +import android.content.Intent +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.Intent +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date + +class ProfileArchiver(val app: App, val profile: Profile) { + companion object { + private const val TAG = "ProfileArchiver" + } + + init { + if (profile.archiveId == null) + profile.archiveId = profile.id + d(TAG, "Processing ${profile.name}#${profile.id}, archiveId = ${profile.archiveId}") + + profile.archived = true + app.db.profileDao().add(profile) + //app.db.metadataDao().setAllSeen(profile.id, true) + app.db.notificationDao().clear(profile.id) + app.db.endpointTimerDao().clear(profile.id) + d(TAG, "Archived profile ${profile.id} saved") + profile.archived = false + + // guess the nearest school year + val today = Date.getToday() + profile.studentSchoolYearStart = when { + today.month <= profile.dateYearEnd.month -> today.year - 1 + else -> today.year + } + + // set default semester dates + profile.dateSemester1Start = Date(profile.studentSchoolYearStart, 9, 1) + profile.dateSemester2Start = Date(profile.studentSchoolYearStart + 1, 2, 1) + profile.dateYearEnd = Date(profile.studentSchoolYearStart + 1, 6, 30) + + val oldId = profile.id + val newId = (app.db.profileDao().lastId ?: profile.id) + 1 + profile.id = newId + profile.subname = "Nowy rok szkolny - ${profile.studentSchoolYearStart}" + profile.studentClassName = null + + d(TAG, "New profile ID for ${profile.name}: ${profile.id}") + + when (profile.loginStoreType) { + LOGIN_TYPE_LIBRUS -> { + profile.removeStudentData("isPremium") + profile.removeStudentData("pushDeviceId") + profile.removeStudentData("startPointsSemester1") + profile.removeStudentData("startPointsSemester2") + profile.removeStudentData("enablePointGrades") + profile.removeStudentData("enableDescriptiveGrades") + } + LOGIN_TYPE_MOBIDZIENNIK -> { + + } + LOGIN_TYPE_VULCAN -> { + // DataVulcan.isApiLoginValid() returns false so it will update the semester + profile.removeStudentData("currentSemesterEndDate") + profile.removeStudentData("studentSemesterId") + profile.removeStudentData("studentSemesterNumber") + profile.removeStudentData("semester1Id") + profile.removeStudentData("semester2Id") + profile.removeStudentData("studentClassId") + } + LOGIN_TYPE_IDZIENNIK -> { + profile.removeStudentData("schoolYearId") + } + LOGIN_TYPE_EDUDZIENNIK -> { + + } + LOGIN_TYPE_PODLASIE -> { + + } + } + + d(TAG, "Processed student data: ${profile.studentData}") + + app.db.profileDao().add(profile) + + if (app.profileId == oldId) { + val intent = Intent( + Intent.ACTION_MAIN, + "profileId" to newId + ) + app.sendBroadcast(intent) + } + } +} 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 new file mode 100644 index 00000000..40601249 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.* + +/** + * Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art + * + * Use https://codepen.io/kubasz/pen/RwwwbGN to easily generate the student data getters/setters + */ +class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = webSessionIdExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_EDUDZIENNIK_WEB + } + } + + override fun generateUserCode() = "$schoolName:$loginEmail:${studentId?.crc32()}" + + private var mLoginEmail: String? = null + var loginEmail: String? + get() { mLoginEmail = mLoginEmail ?: loginStore.getLoginData("email", null); return mLoginEmail } + set(value) { loginStore.putLoginData("email", value); mLoginEmail = value } + + private var mLoginPassword: String? = null + var loginPassword: String? + get() { mLoginPassword = mLoginPassword ?: loginStore.getLoginData("password", null); return mLoginPassword } + set(value) { loginStore.putLoginData("password", value); mLoginPassword = value } + + private var mStudentId: String? = null + var studentId: String? + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } + set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + + private var mSchoolId: String? = null + var schoolId: String? + get() { mSchoolId = mSchoolId ?: profile?.getStudentData("schoolId", null); return mSchoolId } + set(value) { profile?.putStudentData("schoolId", value) ?: return; mSchoolId = value } + + private var mClassId: String? = null + var classId: String? + get() { mClassId = mClassId ?: profile?.getStudentData("classId", null); return mClassId } + set(value) { profile?.putStudentData("classId", value) ?: return; mClassId = value } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebSessionId: String? = null + var webSessionId: String? + get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId } + set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value } + + private var mWebSessionIdExpiryTime: Long? = null + var webSessionIdExpiryTime: Long + get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("webSessionIdExpiryTime", 0L); return mWebSessionIdExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("webSessionIdExpiryTime", value); mWebSessionIdExpiryTime = value } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + private var mCurrentSemester: Int? = null + var currentSemester: Int + get() { mCurrentSemester = mCurrentSemester ?: profile?.getStudentData("currentSemester", 1); return mCurrentSemester ?: 1 } + set(value) { profile?.putStudentData("currentSemester", value) ?: return; mCurrentSemester = value } + + private var mSchoolName: String? = null + var schoolName: String? + get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } + set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } + + val studentEndpoint: String + get() = "Students/$studentId/" + + val schoolEndpoint: String + get() = "Schools/$schoolId/" + + val classStudentEndpoint: String + get() = "Class/$studentId/" + + val schoolClassEndpoint: String + get() = "Schools/$classId/" + + val studentAndClassEndpoint: String + get() = "Students/$studentId/Klass/$classId/" + + val studentAndClassesEndpoint: String + get() = "Students/$studentId/Classes/$classId/" + + val timetableEndpoint: String + get() = "Plan/$studentId/" + + val studentAndTeacherClassEndpoint: String + get() = "Students/$studentId/Teachers/$classId/" + + 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 { + val eventType = EventType(profileId, id, name, colorFromName(name)) + eventTypes.put(id, eventType) + eventType + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt new file mode 100644 index 00000000..23d9d3b1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt @@ -0,0 +1,142 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebGetAnnouncement +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebGetHomework +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin.EdudziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Edudziennik" + } + + val internalErrorList = mutableListOf() + val data: DataEdudziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataEdudziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Edudziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(edudziennikLoginMethods, EdudziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(edudziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + EdudziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: EdudziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) {} + override fun sendMessage(recipients: List, subject: String, text: String) {} + override fun markAllAnnouncementsAsRead() {} + + override fun getAnnouncement(announcement: AnnouncementFull) { + EdudziennikLoginWeb(data) { + EdudziennikWebGetAnnouncement(data, announcement) { + completed() + } + } + } + + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) {} + override fun getRecipientList() {} + + override fun getEvent(eventFull: EventFull) { + EdudziennikLoginWeb(data) { + EdudziennikWebGetHomework(data, eventFull) { + completed() + } + } + } + + override fun firstLogin() { EdudziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED -> { + login() + } + ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID -> { + login() + } + ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS -> { + data() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt new file mode 100644 index 00000000..c3fd17ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature + +const val ENDPOINT_EDUDZIENNIK_WEB_START = 1000 +const val ENDPOINT_EDUDZIENNIK_WEB_TEACHERS = 1001 +const val ENDPOINT_EDUDZIENNIK_WEB_GRADES = 1011 +const val ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE = 1012 +const val ENDPOINT_EDUDZIENNIK_WEB_EXAMS = 1013 +const val ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE = 1014 +const val ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS = 1015 +const val ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK = 1016 +const val ENDPOINT_EDUDZIENNIK_WEB_EVENTS = 1017 +const val ENDPOINT_EDUDZIENNIK_WEB_NOTES = 1018 +const val ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER = 1030 + +val EdudziennikFeatures = listOf( + /* School and team info and subjects */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_EDUDZIENNIK_WEB_START to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Teachers */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_TEACHERS, listOf( + ENDPOINT_EDUDZIENNIK_WEB_TEACHERS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Timetable */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_TIMETABLE, listOf( + ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Grades */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_GRADES, listOf( + ENDPOINT_EDUDZIENNIK_WEB_GRADES to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Agenda */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_AGENDA, listOf( + ENDPOINT_EDUDZIENNIK_WEB_EXAMS to LOGIN_METHOD_EDUDZIENNIK_WEB, + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_EDUDZIENNIK_WEB, + ENDPOINT_EDUDZIENNIK_WEB_EVENTS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Homework */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_HOMEWORK, listOf( + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Behaviour */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_BEHAVIOUR, listOf( + ENDPOINT_EDUDZIENNIK_WEB_NOTES to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Attendance */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ATTENDANCE, listOf( + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Announcements */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf( + ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Lucky number */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt new file mode 100644 index 00000000..7eb58d1c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.* +import pl.szczodrzynski.edziennik.utils.Utils + +class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_EDUDZIENNIK_WEB_START -> { + data.startProgress(R.string.edziennik_progress_endpoint_data) + EdudziennikWebStart(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_TEACHERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teachers) + EdudziennikWebTeachers(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + EdudziennikWebGrades(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + EdudziennikWebTimetable(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_EXAMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_exams) + EdudziennikWebExams(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + EdudziennikWebAttendance(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_announcements) + EdudziennikWebAnnouncements(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + EdudziennikWebHomework(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_EVENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_events) + EdudziennikWebEvents(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_NOTES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notices) + EdudziennikWebNotes(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + EdudziennikWebLuckyNumber(data, lastSync, onSuccess) + } + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt new file mode 100644 index 00000000..f5adfc5d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date + +open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Long?) { + companion object { + private const val TAG = "EdudziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun webGet(tag: String, endpoint: String, xhr: Boolean = false, semester: Int? = null, onSuccess: (text: String) -> Unit) { + val url = "https://dziennikel.appspot.com/" + when (endpoint.endsWith('/') || endpoint.contains('?') || endpoint.isEmpty()) { + true -> endpoint + else -> "$endpoint/" + } + (semester?.let { "?semester=" + if(it == -1) "all" else it } ?: "") + + d(tag, "Request: Edudziennik/Web - $url") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null || response == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (semester == null && url.contains("start")) { + profile?.also { profile -> + val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com") + val semesterCookie = cookies["semester"]?.toIntOrNull() + + semesterCookie?.let { data.currentSemester = it } + + if (semesterCookie == 2 && profile.dateSemester2Start > Date.getToday()) + profile.dateSemester2Start = Date.getToday().stepForward(0, 0, -1) + } + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_WEB_REQUEST) + .withThrowable(e) + .withResponse(response) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + val error = when (response?.code()) { + 402 -> ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS + 403 -> ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED + else -> ERROR_REQUEST_FAILURE + } + data.error(ApiError(tag, error) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("dziennikel.appspot.com", "sessionid", data.webSessionId) + + Request.builder() + .url(url) + .userAgent(EDUDZIENNIK_USER_AGENT) + .apply { + if (xhr) header("X-Requested-With", "XMLHttpRequest") + } + .get() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt new file mode 100644 index 00000000..32ca353c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-26 + */ + +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_ANNOUNCEMENT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebAnnouncements(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebAnnouncements" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.schoolClassEndpoint + "Announcements") { text -> + val doc = Jsoup.parse(text) + + if (doc.getElementsByClass("message").text().trim() != "Brak ogłoszeń.") { + doc.select("table.list tbody tr").forEach { announcementElement -> + val titleElement = announcementElement.child(0).child(0) + + val longId = EDUDZIENNIK_ANNOUNCEMENT_ID.find(titleElement.attr("href"))?.get(1) + ?: return@forEach + val id = longId.crc32() + val subject = titleElement.text() + + val teacherName = announcementElement.child(1).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val dateString = announcementElement.getElementsByClass("datetime").first().text() + val startDate = Date.fromY_m_d(dateString) + val addedDate = Date.fromIsoHm(dateString) + + val announcementObject = Announcement( + profileId = profileId, + id = id, + subject = subject, + text = null, + startDate = startDate, + endDate = null, + teacherId = teacher.id, + addedDate = addedDate + ).also { + it.idString = longId + } + + data.announcementList.add(announcementObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + id, + profile.empty, + profile.empty + )) + } + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt new file mode 100644 index 00000000..7fe18171 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_ENTRIES +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPE +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class EdudziennikWebAttendance(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebAttendance" + } + + private var requestSemester: Int? = null + + init { + if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1 + getAttendances() + } + + private fun getAttendances() { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "Presence", semester = requestSemester) { text -> + + val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map { + val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim()) + val symbol = type?.get(1)?.trim() ?: "?" + val name = type?.get(2)?.trim() ?: "nieznany rodzaj" + return@map Triple( + symbol, + name, + when (name.toLowerCase(Locale.ROOT)) { + "obecność" -> Attendance.TYPE_PRESENT + "nieobecność" -> Attendance.TYPE_ABSENT + "spóźnienie" -> Attendance.TYPE_BELATED + "nieobecność usprawiedliwiona" -> Attendance.TYPE_ABSENT_EXCUSED + "dzień wolny" -> Attendance.TYPE_DAY_FREE + "brak zajęć" -> Attendance.TYPE_DAY_FREE + "oddelegowany" -> Attendance.TYPE_RELEASED + else -> Attendance.TYPE_UNKNOWN + } + ) + } ?: emptyList() + + EDUDZIENNIK_ATTENDANCE_ENTRIES.findAll(text).forEach { attendanceElement -> + val date = Date.fromY_m_d(attendanceElement[1]) + val lessonNumber = attendanceElement[2].toInt() + val attendanceSymbol = attendanceElement[3] + + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) + val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber } + + val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32() + + val (typeSymbol, typeName, baseType) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } + ?: return@forEach + + val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime + ?: return@forEach + + val attendanceObject = Attendance( + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = date, + startTime = lesson?.displayStartTime ?: startTime, + semester = profile.currentSemester, + teacherId = lesson?.displayTeacherId ?: -1, + subjectId = lesson?.displaySubjectId ?: -1 + ).also { + it.lessonNumber = lessonNumber + } + + data.attendanceList.add(attendanceObject) + if (baseType != Attendance.TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + id, + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN + )) + } + } + + if (profile.empty && requestSemester == 1 && data.currentSemester == 2) { + requestSemester = null + getAttendances() + } else { + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) + } + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt new file mode 100644 index 00000000..35c68478 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-1 + */ + +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_EVENT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EVENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebEvents(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebEvents" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.studentAndClassesEndpoint + "KlassEvent", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.getElementsByTag("tr").forEach { eventElement -> + val date = Date.fromY_m_d(eventElement.child(1).text()) + + val titleElement = eventElement.child(2).child(0) + val title = titleElement.text().trim() + + val id = EDUDZIENNIK_EVENT_ID.find(titleElement.attr("href"))?.get(1)?.crc32() + ?: return@forEach + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = null, + topic = title, + color = null, + type = Event.TYPE_CLASS_EVENT, + teacherId = -1, + subjectId = -1, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_CLASS_EVENT)) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_EVENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EVENTS) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EVENTS) } +} 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 new file mode 100644 index 00000000..f6abf637 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-24 + */ + +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_EVENT_TYPE_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EXAM_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebExams(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebExams" + } + + init { profile?.also { profile -> + webGet(TAG, data.studentAndClassEndpoint + "Evaluations", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.select("tr").forEach { examElement -> + val id = EDUDZIENNIK_EXAM_ID.find(examElement.child(0).child(0).attr("href")) + ?.get(1)?.crc32() ?: return@forEach + val topic = examElement.child(0).text().trim() + + val subjectElement = examElement.child(1).child(0) + 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 dateString = examElement.child(2).text().trim() + if (dateString.isBlank()) return@forEach + val date = Date.fromY_m_d(dateString) + + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) + val startTime = lessons.firstOrNull { it.displaySubjectId == subject.id }?.displayStartTime + + val eventTypeElement = examElement.child(3).child(0) + val eventTypeId = EDUDZIENNIK_EVENT_TYPE_ID.find(eventTypeElement.attr("href"))?.get(1) + ?: return@forEach + val eventTypeName = eventTypeElement.text() + val eventType = data.getEventType(eventTypeId, eventTypeName) + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = startTime, + topic = topic, + color = null, + type = eventType.id, + teacherId = -1, + subjectId = subject.id, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureExceptTypes(listOf( + Event.TYPE_HOMEWORK, + Event.TYPE_CLASS_EVENT + ))) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_EXAMS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EXAMS) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt new file mode 100644 index 00000000..5d915e64 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-26 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebGetAnnouncement(override val data: DataEdudziennik, + private val announcement: AnnouncementFull, + val onSuccess: () -> Unit +) : EdudziennikWeb(data, null) { + companion object { + const val TAG = "EdudziennikWebGetAnnouncement" + } + + init { + webGet(TAG, "Announcement/${announcement.idString}") { text -> + val description = Regexes.EDUDZIENNIK_ANNOUNCEMENT_DESCRIPTION.find(text)?.get(1)?.trim() ?: "" + + announcement.text = description + + EventBus.getDefault().postSticky(AnnouncementGetEvent(announcement)) + + data.announcementList.add(announcement) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetHomework.kt new file mode 100644 index 00000000..84171699 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetHomework.kt @@ -0,0 +1,45 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import android.text.Html +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.isNotNullNorEmpty + +class EdudziennikWebGetHomework( + override val data: DataEdudziennik, + val event: EventFull, + val onSuccess: () -> Unit +) : EdudziennikWeb(data, null) { + companion object { + const val TAG = "EdudziennikWebGetHomework" + } + + init { + if (event.attachmentNames.isNotNullNorEmpty()) { + val id = event.attachmentNames!![0] + + webGet(TAG, "Homework/$id") { text -> + val description = Regexes.EDUDZIENNIK_HOMEWORK_DESCRIPTION.find(text)?.get(1)?.trim() + + if (description != null) event.topic = Html.fromHtml(description).toString() + + event.homeworkBody = "" + event.attachmentNames = null + + data.eventList += event + data.eventListReplace = true + + EventBus.getDefault().postSticky(EventGetEvent(event)) + onSuccess() + } + } else { + EventBus.getDefault().postSticky(EventGetEvent(event)) + onSuccess() + } + } +} 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 new file mode 100644 index 00000000..459f13aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -0,0 +1,230 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import android.graphics.Color +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.colorFromCssName +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebGrades(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebGrades" + } + + private var requestSemester: Int? = null + + init { + if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1 + getGrades() + } + + private fun getGrades() { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "start", semester = requestSemester) { text -> + val semester = requestSemester ?: data.currentSemester + + val doc = Jsoup.parse(text) + val subjects = doc.select("#student_grades tbody").firstOrNull()?.children() + + subjects?.forEach { subjectElement -> + if (subjectElement.id().isBlank()) return@forEach + + val subjectId = subjectElement.id().trim() + val subjectName = subjectElement.child(0).text().trim() + val subject = data.getSubject(subjectId, subjectName) + + val gradeType = when { + subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM + else -> TYPE_NORMAL + } + + val gradeCountToAverage = subjectElement.select("#avg").text().isNotBlank() + + val grades = subjectElement.select(".grade[data-edited]") + val gradesInfo = subjectElement.select(".grade-tip") + + val gradeValues = if (grades.isNotEmpty()) { + subjects.select(".avg-$subjectId .grade-tip > p").first() + .text().split('+').map { + val split = it.split('*') + val value = split[1].trim().toFloatOrNull() + val weight = value?.let { split[0].trim().toFloatOrNull() } ?: 0f + + Pair(value ?: 0f, weight) + } + } else emptyList() + + grades.forEachIndexed { index, gradeElement -> + val id = Regexes.EDUDZIENNIK_GRADE_ID.find(gradeElement.attr("href"))?.get(1)?.crc32() + ?: return@forEachIndexed + val (value, weight) = gradeValues[index] + val name = gradeElement.text().trim().let { + if (it.contains(',') || it.contains('.')) { + val replaced = it.replace(',', '.') + val float = replaced.toFloatOrNull() + + if (float != null && float % 1 == 0f) float.toInt().toString() + else it + } else it + } + + val info = gradesInfo[index] + val fullName = info.child(0).text().trim() + val columnName = info.child(4).text().trim() + val comment = info.ownText() + + val description = columnName + if (comment.isNotBlank()) " - $comment" else null + + val teacherName = info.child(1).text() + val teacher = data.getTeacherByLastFirst(teacherName) + + val addedDate = info.child(2).text().split(' ').let { + val day = it[0].toInt() + val month = Utils.monthFromName(it[1]) + val year = it[2].toInt() + + Date(year, month, day).inMillis + } + + val color = Regexes.STYLE_CSS_COLOR.find(gradeElement.attr("style"))?.get(1)?.let { + if (it.startsWith('#')) Color.parseColor(it) + else colorFromCssName(it) + } ?: -1 + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = gradeType, + value = value, + weight = if (gradeCountToAverage) weight else 0f, + color = color, + category = fullName, + description = description, + comment = null, + semester = semester, + teacherId = teacher.id, + subjectId = subject.id, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + val proposed = subjectElement.select(".proposal").firstOrNull()?.text()?.trim() + + if (proposed != null && proposed.isNotBlank()) { + val proposedGradeObject = Grade( + profileId = profileId, + id = (-1 * subject.id) - 1, + name = proposed, + type = when (semester) { + 1 -> TYPE_SEMESTER1_PROPOSED + else -> TYPE_SEMESTER2_PROPOSED + }, + value = proposed.toFloatOrNull() ?: 0f, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id + ) + + data.gradeList.add(proposedGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + proposedGradeObject.id, + profile.empty, + profile.empty + )) + } + + val final = subjectElement.select(".final").firstOrNull()?.text()?.trim() + + if (final != null && final.isNotBlank()) { + val finalGradeObject = Grade( + profileId = profileId, + id = (-1 * subject.id) - 2, + name = final, + type = when (semester) { + 1 -> TYPE_SEMESTER1_FINAL + else -> TYPE_SEMESTER2_FINAL + }, + value = final.toFloatOrNull() ?: 0f, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id + ) + + data.gradeList.add(finalGradeObject) + data.metadataList.add(Metadata( + data.profileId, + Metadata.TYPE_GRADE, + finalGradeObject.id, + profile.empty, + profile.empty + )) + } + } + + if (!subjects.isNullOrEmpty()) { + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_POINT_SUM, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL + ).map { + DataRemoveModel.Grades.semesterWithType(semester, it) + }) + } + + if (profile.empty && requestSemester == 1 && data.currentSemester == 2) { + requestSemester = null + getGrades() + } else { + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_GRADES) + } + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_GRADES) } +} 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 new file mode 100644 index 00000000..169a5127 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +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_HOMEWORK_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebHomework(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebHomework" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.courseStudentEndpoint + "Homework", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + if (doc.getElementsByClass("message").text().trim() != "Brak prac domowych") { + doc.getElementsByTag("tr").forEach { homeworkElement -> + val dateElement = homeworkElement.getElementsByClass("date").first().child(0) + val idStr = EDUDZIENNIK_HOMEWORK_ID.find(dateElement.attr("href"))?.get(1) ?: return@forEach + val id = idStr.crc32() + val date = Date.fromY_m_d(dateElement.text()) + + val subjectElement = homeworkElement.child(1).child(0) + val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) + ?: return@forEach + val subjectName = subjectElement.text() + val subject = data.getSubject(subjectId, subjectName) + + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) + val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime + + val teacherName = homeworkElement.child(2).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val topic = homeworkElement.child(4).text()?.trim() + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = startTime, + topic = topic ?: "", + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacher.id, + subjectId = subject.id, + teamId = data.teamClass?.id ?: -1 + ) + + eventObject.attachmentNames = mutableListOf(idStr) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + id, + profile.empty, + profile.empty + )) + } + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt new file mode 100644 index 00000000..db8164c8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebLuckyNumber" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.schoolEndpoint + "Lucky", xhr = true) { text -> + text.toIntOrNull()?.also { luckyNumber -> + val luckyNumberObject = LuckyNumber( + profileId = profileId, + date = Date.getToday(), + number = luckyNumber + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile.empty + )) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt new file mode 100644 index 00000000..35a78168 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-1 + */ + +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_NOTE_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_NOTES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebNotes(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebNotes" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.classStudentEndpoint + "RegistryNotesStudent", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.getElementsByTag("tr").forEach { noteElement -> + val dateElement = noteElement.getElementsByClass("date").first().child(0) + val addedDate = Date.fromY_m_d(dateElement.text()).inMillis + + val id = EDUDZIENNIK_NOTE_ID.find(dateElement.attr("href"))?.get(0)?.crc32() + ?: return@forEach + + val teacherName = noteElement.child(1).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val description = noteElement.child(3).text() + + val noticeObject = Notice( + profileId = profileId, + id = id, + type = Notice.TYPE_NEUTRAL, + semester = profile.currentSemester, + text = description, + category = null, + points = null, + teacherId = teacher.id, + addedDate = addedDate + ) + + data.noticeList.add(noticeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_NOTICE, + id, + profile.empty, + profile.empty + )) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_NOTES, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_NOTES) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_NOTES) } +} 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 new file mode 100644 index 00000000..66cb7a59 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TEAM_MISSING +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECTS_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.firstLettersName +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebStart(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebStart" + } + + init { + webGet(TAG, data.studentEndpoint + "start") { text -> + getSchoolAndTeam(text) + getSubjects(text) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_START, MONTH) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_START) + } + } + + private fun getSchoolAndTeam(text: String) { + val schoolId = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_ID.find(text)?.get(1)?.trim() + val schoolLongName = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_NAME.find(text)?.get(1)?.trim() + data.schoolId = schoolId + + val classId = Regexes.EDUDZIENNIK_CLASS_DETAIL_ID.find(text)?.get(1)?.trim() + val className = Regexes.EDUDZIENNIK_CLASS_DETAIL_NAME.find(text)?.get(1)?.trim() + data.classId = classId + + if (classId == null || className == null || schoolId == null || schoolLongName == null) { + data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TEAM_MISSING) + .withApiResponse(text)) + return + } + + val schoolName = schoolId.crc32().toString() + schoolLongName.firstLettersName + "_edu" + data.schoolName = schoolName + + val teamId = classId.crc32() + val teamCode = "$schoolName:$className" + + val teamObject = Team( + data.profileId, + teamId, + className, + Team.TYPE_CLASS, + teamCode, + -1 + ) + + data.teamClass = teamObject + data.teamList.put(teamObject.id, teamObject) + } + + private fun getSubjects(text: String) { + EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach { + val id = it[1].trim() + val name = it[2].trim() + data.getSubject(id, name) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt new file mode 100644 index 00000000..d2f3ba15 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHERS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_TEACHERS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebTeachers(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebTeachers" + } + + init { + webGet(TAG, data.studentAndTeacherClassEndpoint + "grid") { text -> + EDUDZIENNIK_TEACHERS.findAll(text).forEach { + val lastName = it[1].trim() + val firstName = it[2].trim() + data.getTeacher(firstName, lastName) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_TEACHERS, MONTH) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TEACHERS) + } + } +} 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 new file mode 100644 index 00000000..94db15ef --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +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 +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class EdudziennikWebTimetable(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebTimetable" + } + + init { data.profile?.also { profile -> + + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d + + val weekStart = Date.fromY_m_d(getDate) + val weekEnd = weekStart.clone().stepForward(0, 0, 6) + + webGet(TAG, data.timetableEndpoint + "print?date=$getDate") { text -> + val doc = Jsoup.parse(text) + + val dataDays = mutableListOf() + val dataStart = weekStart.clone() + while (dataStart <= weekEnd) { + dataDays += dataStart.value + dataStart.stepForward(0, 0, 1) + } + + val table = doc.select("#Schedule tbody").first() + + if (!table.text().contains("Brak planu lekcji.")) { + table.children().forEach { row -> + val rowElements = row.children() + + val lessonNumber = rowElements[0].text().toInt() + + val times = rowElements[1].text().split('-') + val startTime = Time.fromH_m(times[0].trim()) + val endTime = Time.fromH_m(times[1].trim()) + + data.lessonRanges.singleOrNull { + it.lessonNumber == lessonNumber && it.startTime == startTime && it.endTime == endTime + } ?: run { + data.lessonRanges.put(lessonNumber, LessonRange(profileId, lessonNumber, startTime, endTime)) + } + + rowElements.subList(2, rowElements.size).forEachIndexed { index, lesson -> + val course = lesson.select(".course").firstOrNull() ?: return@forEachIndexed + val info = course.select("span > span") + + if (info.isEmpty()) return@forEachIndexed + + val type = when (course.hasClass("substitute")) { + true -> Lesson.TYPE_CHANGE + else -> Lesson.TYPE_NORMAL + } + + /* Getting subject */ + + val subjectElement = info[0].child(0) + val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) + ?: return@forEachIndexed + val subjectName = subjectElement.text().trim() + val subject = data.getSubject(subjectId, subjectName) + + /* Getting teacher */ + + val teacherId = if (info.size >= 2) { + val teacherElement = info[1].child(0) + val teacherLongId = EDUDZIENNIK_TEACHER_ID.find(teacherElement.attr("href"))?.get(1) + val teacherName = teacherElement.text().trim() + data.getTeacherByLastFirst(teacherName, teacherLongId).id + } else null + + val lessonObject = Lesson(profileId, -1).also { + it.type = type + it.date = weekStart.clone().stepForward(0, 0, index) + it.lessonNumber = lessonNumber + it.startTime = startTime + it.endTime = endTime + it.subjectId = subject.id + it.teacherId = teacherId + it.teamId = data.teamClass?.id + + it.id = it.buildId() + } + + data.lessonList.add(lessonObject) + dataDays.remove(lessonObject.date!!.value) + + if (type != Lesson.TYPE_NORMAL) { + val seen = profile.empty || lessonObject.date!! < Date.getToday() + + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LESSON_CHANGE, + lessonObject.id, + seen, + seen + )) + } + } + } + } + + for (day in dataDays) { + val lessonDate = Date.fromValue(day) + data.lessonList += Lesson(profileId, lessonDate.value.toLong()).apply { + type = Lesson.TYPE_NO_LESSONS + date = lessonDate + } + } + + d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") + + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt new file mode 100644 index 00000000..a93632b1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ACCOUNT_NAME_START +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_STUDENTS_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getShortName +import pl.szczodrzynski.edziennik.set + +class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikFirstLogin" + } + + private val web = EdudziennikWeb(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_EDUDZIENNIK + var firstProfileId = loginStoreId + + EdudziennikLoginWeb(data) { + web.webGet(TAG, "") { text -> + val accountNameLong = EDUDZIENNIK_ACCOUNT_NAME_START.find(text)?.get(1)?.fixName() + + EDUDZIENNIK_STUDENTS_START.findAll(text).forEach { + val studentId = it[1] + val studentNameLong = it[2].fixName() + + if (studentId.isBlank() || studentNameLong.isBlank()) return@forEach + + val studentNameShort = studentNameLong.getShortName() + val accountName = if (accountNameLong == studentNameLong) null else accountNameLong + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.loginEmail, + studentNameLong, + studentNameShort, + accountName + ).apply { + studentData["studentId"] = studentId + } + profileList.add(profile) + } + + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt new file mode 100644 index 00000000..1cb2fd07 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.utils.Utils + +class EdudziennikLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + data.progress(data.progressStep) + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_EDUDZIENNIK_WEB -> { + data.startProgress(R.string.edziennik_progress_login_edudziennik_web) + EdudziennikLoginWeb(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt new file mode 100644 index 00000000..6b1615a2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.utils.Utils.d + +class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikLoginWeb" + } + + init { run { + if (data.isWebLoginValid()) { + onSuccess() + } + else { + data.app.cookieJar.clear("dziennikel.appspot.com") + if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + private fun loginWithCredentials() { + d(TAG, "Request: Edudziennik/Login/Web - https://dziennikel.appspot.com/login/?next=/") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null || response == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + val url = response.raw().request().url().toString() + + if (!url.contains("Student")) { + when { + text.contains("Wprowadzono nieprawidłową nazwę użytkownika lub hasło.") -> ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN + else -> ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com") + val sessionId = cookies["sessionid"] + + if (sessionId == null) { + data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID) + .withResponse(response) + .withApiResponse(text)) + return + } + + data.webSessionId = sessionId + data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */ + onSuccess() + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("https://dziennikel.appspot.com/login/?next=/") + .userAgent(EDUDZIENNIK_USER_AGENT) + .contentType("application/x-www-form-urlencoded") + .addParameter("email", data.loginEmail) + .addParameter("password", data.loginPassword) + .addParameter("auth_method", "password") + .addParameter("next", "/") + .post() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt new file mode 100644 index 00000000..a968818a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/DownloadAttachment.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-5-14 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.helper + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import pl.szczodrzynski.edziennik.data.api.ERROR_FILE_DOWNLOAD +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE +import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class DownloadAttachment( + fileUrl: String, + val onSuccess: (file: File) -> Unit, + val onProgress: (written: Long, total: Long) -> Unit, + val onError: (apiError: ApiError) -> Unit +) { + companion object { + private const val TAG = "DownloadAttachment" + } + + init { + val targetFile = Utils.getStorageDir() + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + this@DownloadAttachment.onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + onError(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(fileUrl) + .userAgent(SYSTEM_USER_AGENT) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/OneDriveDownloadAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/OneDriveDownloadAttachment.kt new file mode 100644 index 00000000..25b78527 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/helper/OneDriveDownloadAttachment.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-7. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.helper + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.ERROR_ONEDRIVE_DOWNLOAD +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE +import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class OneDriveDownloadAttachment( + app: App, + fileUrl: String, + val onSuccess: (file: File) -> Unit, + val onProgress: (written: Long, total: Long) -> Unit, + val onError: (apiError: ApiError) -> Unit +) { + companion object { + private const val TAG = "OneDriveDownloadAttachment" + } + + init { + Request.builder() + .url(fileUrl) + .userAgent(SYSTEM_USER_AGENT) + .withClient(app.httpLazy) + .callback(object : TextCallbackHandler() { + override fun onSuccess(text: String, response: Response) { + val location = response.headers().get("Location") + // https://onedrive.live.com/redir?resid=D75496A2EB87531C!706&authkey=!ABjZeh3pHMqj11Q + if (location?.contains("onedrive.live.com/redir?resid=") != true) { + onError(ApiError(TAG, ERROR_ONEDRIVE_DOWNLOAD) + .withApiResponse(text) + .withResponse(response)) + return + } + val url = location + .replace("onedrive.live.com/redir?resid=", "storage.live.com/items/") + .replace("?", "&") + .replaceFirst("&", "?") + downloadFile(url) + } + + override fun onFailure(response: Response, throwable: Throwable) { + onError(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + }) + .build() + .enqueue() + } + + private fun downloadFile(url: String) { + val targetFile = Utils.getStorageDir() + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + onError(ApiError(TAG, ERROR_ONEDRIVE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_ONEDRIVE_DOWNLOAD) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + this@OneDriveDownloadAttachment.onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + onError(ApiError(TAG, ERROR_ONEDRIVE_DOWNLOAD) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + onError(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(SYSTEM_USER_AGENT) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt similarity index 84% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt index a774129d..3e974539 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt @@ -2,18 +2,17 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-25. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik import androidx.core.util.set -import okhttp3.Cookie import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB -import pl.szczodrzynski.edziennik.api.v2.models.Data -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject -import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Subject +import pl.szczodrzynski.edziennik.data.db.entity.Teacher class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { @@ -24,23 +23,15 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data( loginMethods.clear() if (isWebLoginValid()) { loginMethods += LOGIN_METHOD_IDZIENNIK_WEB - app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("ASP.NET_SessionId_iDziennik") - .value(webSessionId!!) - .domain("iuczniowie.progman.pl") - .secure().httpOnly().build(), - Cookie.Builder() - .name(".ASPXAUTH") - .value(webAuth!!) - .domain("iuczniowie.progman.pl") - .secure().httpOnly().build() - )) + app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId) + app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth) } if (isApiLoginValid()) loginMethods += LOGIN_METHOD_IDZIENNIK_API } + override fun generateUserCode() = "$webSchoolName:$webUsername:$registerId" + private var mLoginExpiryTime: Long? = null var loginExpiryTime: Long get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L } @@ -79,6 +70,11 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data( get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth } set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value } + private var mWebSelectedRegister: Int? = null + var webSelectedRegister: Int + get() { mWebSelectedRegister = mWebSelectedRegister ?: loginStore.getLoginData("webSelectedRegister", 0); return mWebSelectedRegister ?: 0 } + set(value) { loginStore.putLoginData("webSelectedRegister", value); mWebSelectedRegister = value } + /* _ /\ (_) / \ _ __ _ @@ -128,7 +124,8 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data( subjectList.singleOrNull { it.id == id } if (subject == null) { - subject = Subject(profileId, id ?: name.crc16().toLong(), name, shortName) + subject = Subject(profileId, id + ?: name.crc16().toLong(), name, shortName) subjectList[subject.id] = subject } return subject @@ -138,10 +135,12 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data( val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } return validateTeacher(teacher, firstName, lastName) } + fun getTeacher(firstNameChar: Char, lastName: String): Teacher { val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } return validateTeacher(teacher, firstNameChar.toString(), lastName) } + fun getTeacherByLastFirst(nameLastFirst: String): Teacher { val nameParts = nameLastFirst.split(" ") return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0]) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt new file mode 100644 index 00000000..e1550632 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin.IdziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Idziennik" + } + + val internalErrorList = mutableListOf() + val data: DataIdziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataIdziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Idziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(idziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + IdziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: IdziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetMessage(data, message) { + completed() + } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_IDZIENNIK_API) { + IdziennikWebSendMessage(data, recipients, subject, text) { + completed() + } + } + } + + override fun markAllAnnouncementsAsRead() {} + override fun getAnnouncement(announcement: AnnouncementFull) {} + + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + if (owner is Message) { + IdziennikWebGetAttachment(data, owner, attachmentId, attachmentName) { + completed() + } + } + else if (owner is Event) { + IdziennikWebGetHomeworkAttachment(data, owner, attachmentId, attachmentName) { + completed() + } + } + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetRecipientList(data) { + completed() + } + } + } + + override fun getEvent(eventFull: EventFull) { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetHomework(data, eventFull) { + completed() + } + } + } + + override fun firstLogin() { IdziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION, + ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH, + ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER, + ERROR_IDZIENNIK_WEB_ACCESS_DENIED, + ERROR_IDZIENNIK_API_ACCESS_DENIED -> { + data.loginMethods.remove(LOGIN_METHOD_IDZIENNIK_WEB) + data.prepareFor(idziennikLoginMethods, LOGIN_METHOD_IDZIENNIK_WEB) + data.loginExpiryTime = 0 + login() + } + ERROR_IDZIENNIK_API_NO_REGISTER -> { + data() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt similarity index 88% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt index c83c9690..02aaabb2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt @@ -2,15 +2,16 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-25. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.models.Feature +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030 const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040 const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050 const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060 +const val ENDPOINT_IDZIENNIK_WEB_HOMEWORK = 1061 const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070 const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080 const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090 @@ -34,6 +35,10 @@ val IdziennikFeatures = listOf( ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_HOMEWORK, listOf( + ENDPOINT_IDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf( ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt similarity index 89% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt index 66a87dfa..8da75603 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt @@ -2,7 +2,7 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-29. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data import com.google.gson.JsonArray import com.google.gson.JsonElement @@ -11,14 +11,14 @@ import com.google.gson.JsonParser import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.TextCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.Utils import java.net.HttpURLConnection -open class IdziennikApi(open val data: DataIdziennik) { +open class IdziennikApi(open val data: DataIdziennik, open val lastSync: Long?) { companion object { const val TAG = "IdziennikApi" } @@ -55,6 +55,7 @@ open class IdziennikApi(open val data: DataIdziennik) { } error?.let { code -> when (code) { + "Uczeń nie posiada aktywnej pozycji w dzienniku" -> ERROR_IDZIENNIK_API_NO_REGISTER "Authorization has been denied for this request." -> ERROR_IDZIENNIK_API_ACCESS_DENIED else -> ERROR_IDZIENNIK_API_OTHER }.let { errorCode -> @@ -107,6 +108,7 @@ open class IdziennikApi(open val data: DataIdziennik) { } } } + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) .allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR) .callback(callback) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt similarity index 55% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt index 64064a83..8acb04cb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt @@ -2,14 +2,14 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-25. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.idziennik.* -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiCurrentRegister -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiMessagesInbox -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiMessagesSent -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.web.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiCurrentRegister +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesInbox +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.* import pl.szczodrzynski.edziennik.utils.Utils class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) { @@ -30,56 +30,62 @@ class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) { onSuccess() return } - useEndpoint(data.targetEndpointIds.removeAt(0)) { + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> data.progress(data.progressStep) nextEndpoint(onSuccess) } } - private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { - Utils.d(TAG, "Using endpoint $endpointId") + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") when (endpointId) { ENDPOINT_IDZIENNIK_WEB_TIMETABLE -> { data.startProgress(R.string.edziennik_progress_endpoint_timetable) - IdziennikWebTimetable(data, onSuccess) + IdziennikWebTimetable(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_GRADES -> { data.startProgress(R.string.edziennik_progress_endpoint_grades) - IdziennikWebGrades(data, onSuccess) + IdziennikWebGrades(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -> { data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades) - IdziennikWebProposedGrades(data, onSuccess) + IdziennikWebProposedGrades(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_EXAMS -> { data.startProgress(R.string.edziennik_progress_endpoint_exams) - IdziennikWebExams(data, onSuccess) + IdziennikWebExams(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + IdziennikWebHomework(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_NOTICES -> { data.startProgress(R.string.edziennik_progress_endpoint_notices) - IdziennikWebNotices(data, onSuccess) + IdziennikWebNotices(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -> { data.startProgress(R.string.edziennik_progress_endpoint_announcements) - IdziennikWebAnnouncements(data, onSuccess) + IdziennikWebAnnouncements(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -> { data.startProgress(R.string.edziennik_progress_endpoint_attendance) - IdziennikWebAttendance(data, onSuccess) + IdziennikWebAttendance(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> { data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) - IdziennikApiCurrentRegister(data, onSuccess) + IdziennikApiCurrentRegister(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) - IdziennikApiMessagesInbox(data, onSuccess) + IdziennikApiMessagesInbox(data, lastSync, onSuccess) } ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) - IdziennikApiMessagesSent(data, onSuccess) + IdziennikApiMessagesSent(data, lastSync, onSuccess) } - else -> onSuccess() + else -> onSuccess(endpointId) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt similarity index 61% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt index 2bae8843..b871cd37 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt @@ -2,22 +2,25 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-25. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data import com.google.gson.JsonArray import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSwitchRegister +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR import java.net.HttpURLConnection.HTTP_UNAUTHORIZED -open class IdziennikWeb(open val data: DataIdziennik) { +open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?) { companion object { const val TAG = "IdziennikWeb" } @@ -39,6 +42,24 @@ open class IdziennikWeb(open val data: DataIdziennik) { return } + if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GET_RECIPIENT_LIST) { + data.error(ApiError(tag, ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION) + .withResponse(response) + .withApiResponse(json)) + return + } + + if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GRADES) { + // special override for accounts where displaying grades + // for another student requires switching it manually + if (data.registerId != data.webSelectedRegister) { + IdziennikWebSwitchRegister(data, data.registerId) { + webApiGet(tag, endpoint, parameters, onSuccess) + } + return + } + } + when { response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR @@ -94,6 +115,7 @@ open class IdziennikWeb(open val data: DataIdziennik) { is Long -> json.addProperty(name, value) is Float -> json.addProperty(name, value) is Char -> json.addProperty(name, value) + is Boolean -> json.addProperty(name, value) } } setJsonBody(json) @@ -105,7 +127,7 @@ open class IdziennikWeb(open val data: DataIdziennik) { .enqueue() } - fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) { + fun webGet(tag: String, endpoint: String, parameters: Map = emptyMap(), onSuccess: (text: String) -> Unit) { d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint") val callback = object : TextCallbackHandler() { @@ -150,7 +172,65 @@ open class IdziennikWeb(open val data: DataIdziennik) { Request.builder() .url("$IDZIENNIK_WEB_URL/$endpoint") .userAgent(IDZIENNIK_USER_AGENT) - .get() + .apply { + if (parameters.isEmpty()) get() + else post() + + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .callback(callback) + .build() + .enqueue() + } + + fun webGetFile(tag: String, endpoint: String, targetFile: File, parameters: Map, + onSuccess: (file: File) -> Unit, onProgress: (written: Long, total: Long) -> Unit) { + + d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .apply { + parameters.forEach { (k, v) -> addParameter(k, v) } + } + .contentType("application/x-www-form-urlencoded") + .post() .callback(callback) .build() .enqueue() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt similarity index 67% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt index 2366e625..d5e59596 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt @@ -2,16 +2,16 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-29. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.DAY -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_CURRENT_REGISTER -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi -import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.getInt import pl.szczodrzynski.edziennik.getJsonObject import pl.szczodrzynski.edziennik.getString @@ -19,27 +19,26 @@ import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time class IdziennikApiCurrentRegister(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { companion object { private const val TAG = "IdziennikApiCurrentRegister" } init { - data.profile?.luckyNumber = -1 - data.profile?.luckyNumberDate = null - apiGet(TAG, IDZIENNIK_API_CURRENT_REGISTER) { json -> if (json !is JsonObject) { - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER) return@apiGet } var nextSync = System.currentTimeMillis() + 14*DAY*1000 val settings = json.getJsonObject("ustawienia")?.apply { - profile?.dateSemester1Start = getString("poczatekSemestru1")?.let { Date.fromY_m_d(it) } - profile?.dateSemester2Start = getString("koniecSemestru1")?.let { Date.fromY_m_d(it).stepForward(0, 0, 1) } - profile?.dateYearEnd = getString("koniecSemestru2")?.let { Date.fromY_m_d(it) } + getString("poczatekSemestru1")?.let { profile?.dateSemester1Start = Date.fromY_m_d(it) } + getString("koniecSemestru1")?.let { profile?.dateSemester2Start = Date.fromY_m_d(it).stepForward(0, 0, 1) } + getString("koniecSemestru2")?.let { profile?.dateYearEnd = Date.fromY_m_d(it) } } json.getInt("szczesliwyNumerek")?.let { luckyNumber -> @@ -69,9 +68,9 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, val luckyNumberObject = LuckyNumber( - data.profileId, - Date.getToday(), - luckyNumber + profileId = data.profileId, + date = luckyNumberDate, + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -80,15 +79,14 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, profileId, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + true, + data.profile?.empty ?: false )) } data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync) - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt new file mode 100644 index 00000000..622068e2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api + +import com.google.gson.JsonArray +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_INBOX +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.getBoolean +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils.crc32 +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikApiMessagesInbox(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { + companion object { + private const val TAG = "IdziennikApiMessagesInbox" + } + + init { + apiGet(TAG, IDZIENNIK_API_MESSAGES_INBOX) { json -> + if (json !is JsonArray) { + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX) + return@apiGet + } + + json.asJsonObjectList().forEach { jMessage -> + val subject = jMessage.getString("tytul") ?: "" + if (subject.contains("(") && subject.startsWith("iDziennik - ")) + return@forEach + if (subject.startsWith("Uwaga dla ucznia (klasa:")) + return@forEach + + val messageIdStr = jMessage.getString("id") + val messageId = crc32((messageIdStr + "0").toByteArray()) + + var body = "[META:$messageIdStr;-1]" + body += jMessage.getString("tresc")?.replace("\n".toRegex(), "
    ") + + val readDate = if (jMessage.getBoolean("odczytana") == true) Date.fromIso(jMessage.getString("wersjaRekordu")) else 0 + val sentDate = Date.fromIso(jMessage.getString("dataWyslania")) + + val sender = jMessage.getAsJsonObject("nadawca") + var firstName = sender.getString("imie") + var lastName = sender.getString("nazwisko") + if (firstName.isNullOrEmpty() || lastName.isNullOrEmpty()) { + firstName = "usunięty" + lastName = "użytkownik" + } + val rTeacher = data.getTeacher( + firstName, + lastName + ) + rTeacher.loginId = /*sender.getString("id") + ":" + */sender.getString("usr") + rTeacher.setTeacherType(Teacher.TYPE_OTHER) + + val message = Message( + profileId = profileId, + id = messageId, + type = if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED, + subject = subject, + body = body, + senderId = rTeacher.id, + addedDate = sentDate + ) + + val messageRecipient = MessageRecipient( + profileId, + -1 /* me */, + -1, + readDate, + /*messageId*/ messageId + ) + + data.messageList.add(message) + data.messageRecipientList.add(messageRecipient) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + message.id, + readDate > 0, + readDate > 0 || profile?.empty ?: false + )) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt similarity index 61% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesSent.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt index 6c809d8b..87b3264d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt @@ -2,25 +2,27 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-30. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api import com.google.gson.JsonArray import pl.szczodrzynski.edziennik.DAY import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_MESSAGES_SENT -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi import pl.szczodrzynski.edziennik.asJsonObjectList -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.Utils.crc32 import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikApiMessagesSent(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { companion object { private const val TAG = "IdziennikApiMessagesSent" } @@ -28,7 +30,7 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, init { apiGet(TAG, IDZIENNIK_API_MESSAGES_SENT) { json -> if (json !is JsonArray) { - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT) return@apiGet } @@ -44,13 +46,13 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, val sentDate = Date.fromIso(jMessage.get("dataWyslania").asString) val message = Message( - profileId, - messageId, - subject, - body, - TYPE_SENT, - -1, - -1 + profileId = profileId, + id = messageId, + type = TYPE_SENT, + subject = subject, + body = body, + senderId = null, + addedDate = sentDate ) for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) { @@ -62,7 +64,7 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, lastName = "użytkownik" } val rTeacher = data.getTeacher(firstName, lastName) - rTeacher.loginId = recipient.get("id").asString + ":" + recipient.get("usr").asString + rTeacher.loginId = /*recipient.get("id").asString + ":" + */recipient.get("usr").asString val messageRecipient = MessageRecipient( profileId, @@ -74,12 +76,12 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, data.messageRecipientIgnoreList.add(messageRecipient) } - data.messageIgnoreList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)) + data.messageList.add(message) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true)) } data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt new file mode 100644 index 00000000..531cbeeb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebAnnouncements(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebAnnouncements" + } + + init { + val param = JsonObject() + param.add("parametryFiltrow", JsonArray()) + + webApiGet(TAG, IDZIENNIK_WEB_ANNOUNCEMENTS, mapOf( + "uczenId" to (data.studentId ?: ""), + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + for (jAnnouncementEl in json.getAsJsonArray("ListK")) { + val jAnnouncement = jAnnouncementEl.asJsonObject + // jAnnouncement + val announcementId = jAnnouncement.getLong("Id") ?: -1 + + val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "") + val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis() + val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) } + + val announcementObject = Announcement( + profileId = profileId, + id = announcementId, + subject = jAnnouncement.get("Temat").asString, + text = jAnnouncement.get("Tresc").asString, + startDate = startDate, + endDate = null, + teacherId = rTeacher.id, + addedDate = addedDate + ) + data.announcementList.add(announcementObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + announcementObject.id, + profile?.empty ?: false, + profile?.empty ?: false + )) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt new file mode 100644 index 00000000..e77c9eac --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt @@ -0,0 +1,180 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import pl.szczodrzynski.edziennik.crc16 +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class IdziennikWebAttendance(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebAttendance" + } + + private var attendanceYear = Date.getToday().year + private var attendanceMonth = Date.getToday().month + private var attendancePrevMonthChecked = false + + init { + getAttendance() + } + + private fun getAttendance() { + webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf( + "idPozDziennika" to data.registerId, + "mc" to attendanceMonth, + "rok" to attendanceYear, + "dataTygodnia" to "" + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) { + val jAttendance = jAttendanceEl.asJsonObject + // jAttendance + val type = jAttendance.get("TypObecnosci").asInt + + // skip "zajęcia nie odbyły się" and "Ferie" + if (type == 5 || type == 7) + continue + + val date = Date.fromY_m_d(jAttendance.get("Data").asString) + val time = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) + if (date.combineWith(time) > System.currentTimeMillis()) + continue + + val id = jAttendance.get("IdLesson").asString.crc16().toLong() + val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "") + val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString) + + var baseType = TYPE_UNKNOWN + var typeName = "nieznany rodzaj" + var typeSymbol: String? = null + var typeColor: Long? = null + + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosciUcznia_lmt637231494660000000.js */ + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosci_lmt637231494660000000.css */ + when (type) { + 1 -> { + baseType = TYPE_ABSENT_EXCUSED + typeName = "nieobecność usprawiedliwiona" + typeColor = 0xffffe099 + } + 2 -> { + baseType = TYPE_BELATED + typeName = "spóźnienie" + typeColor = 0xffffffaa + } + 3 -> { + baseType = TYPE_ABSENT + typeName = "nieobecność nieusprawiedliwiona" + typeColor = 0xffffad99 + } + 4, 9 -> { + baseType = TYPE_RELEASED + if (type == 4) { + typeName = "zwolnienie" + typeColor = 0xffa8beff + } + if (type == 9) { + typeName = "zwolniony / obecny" + typeSymbol = "zb" + typeColor = 0xffff69b4 + } + } + 8 -> { + baseType = TYPE_PRESENT_CUSTOM + typeName = "wycieczka" + typeSymbol = "w" + typeColor = null + } + 0 -> { + baseType = TYPE_PRESENT + typeName = "obecny" + typeColor = 0xffccffcc + } + } + + val semester = profile?.dateToSemester(date) ?: 1 + + val attendanceObject = Attendance( + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeColor = typeColor?.toInt(), + date = date, + startTime = time, + semester = semester, + teacherId = rTeacher.id, + subjectId = rSubject.id + ).also { + it.lessonTopic = jAttendance.getString("PrzedmiotTemat") + it.lessonNumber = jAttendance.getInt("Godzina") + } + + data.attendanceList.add(attendanceObject) + if (attendanceObject.baseType != TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + attendanceObject.id, + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN, + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN + )) + } + } + + val attendanceDateValue = attendanceYear * 10000 + attendanceMonth * 100 + if (profile?.empty == true && attendanceDateValue > profile?.getSemesterStart(1)?.value ?: 99999999) { + attendancePrevMonthChecked = true // do not need to check prev month later + attendanceMonth-- + if (attendanceMonth < 1) { + attendanceMonth = 12 + attendanceYear-- + } + getAttendance() + } else if (!attendancePrevMonthChecked /* get also the previous month */) { + attendanceMonth-- + if (attendanceMonth < 1) { + attendanceMonth = 12 + attendanceYear-- + } + attendancePrevMonthChecked = true + getAttendance() + } else { + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt new file mode 100644 index 00000000..25c06945 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +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 java.util.* + +class IdziennikWebExams(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebExams" + } + + private var examsYear = Date.getToday().year + private var examsMonth = Date.getToday().month + private var examsMonthsChecked = 0 + private var examsNextMonthChecked = false // TO DO temporary // no more // idk + + init { + getExams() + } + + private fun getExams() { + val param = JsonObject().apply { + addProperty("strona", 1) + addProperty("iloscNaStrone", "99") + addProperty("iloscRekordow", -1) + addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu") + addProperty("kierunekSort", 0) + addProperty("maxIloscZaznaczonych", 0) + addProperty("panelFiltrow", 0) + } + + webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf( + "idP" to data.registerId, + "rok" to examsYear, + "miesiac" to examsMonth, + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { exam -> + val id = exam.getLong("_recordId") ?: return@forEach + val examDate = Date.fromY_m_d(exam.getString("data") ?: return@forEach) + val subjectName = exam.getString("przedmiot") ?: return@forEach + val subjectId = data.getSubject(subjectName, null, subjectName).id + val teacherName = exam.getString("wpisal") ?: return@forEach + val teacherId = data.getTeacherByLastFirst(teacherName).id + val topic = exam.getString("zakres")?.trim() ?: "" + + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, examDate) + val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime + + val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) { + "sprawdzian/praca klasowa", + "sprawdzian", + "praca klasowa" -> Event.TYPE_EXAM + "kartkówka" -> Event.TYPE_SHORT_QUIZ + else -> Event.TYPE_EXAM + } + + val eventObject = Event( + profileId = profileId, + id = id, + date = examDate, + time = startTime, + topic = topic, + color = null, + type = eventType, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + eventObject.id, + profile?.empty ?: false, + profile?.empty ?: false + )) + } + + if (profile?.empty == true && examsMonthsChecked < 3 /* how many months backwards to check? */) { + examsMonthsChecked++ + examsMonth-- + if (examsMonth < 1) { + examsMonth = 12 + examsYear-- + } + getExams() + } else if (!examsNextMonthChecked /* get also one month forward */) { + val showDate = Date.getToday().stepForward(0, 1, 0) + examsYear = showDate.year + examsMonth = showDate.month + examsNextMonthChecked = true + getExams() + } else { + data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_EXAMS) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt new file mode 100644 index 00000000..b7a462f6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-28 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_ATTACHMENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class IdziennikWebGetAttachment(override val data: DataIdziennik, + val owner: Any, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + const val TAG = "IdziennikWebGetAttachment" + } + + init { + val message = owner as Message + + val messageId = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex().find(message.body ?: "")?.get(2) ?: -1 + val targetFile = File(Utils.getStorageDir(), attachmentName) + + webGetFile(TAG, IDZIENNIK_WEB_GET_ATTACHMENT, targetFile, mapOf( + "id" to messageId, + "fileName" to attachmentName + ), { file -> + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.ownerId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + EventBus.getDefault().postSticky(event) + + onSuccess() + + }) { written, _ -> + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().postSticky(event) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomework.kt new file mode 100644 index 00000000..12d804db --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomework.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-1. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.getBoolean +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString + +class IdziennikWebGetHomework(override val data: DataIdziennik, + val event: EventFull, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebGetHomework" + } + + init { + webApiGet(TAG, IDZIENNIK_WEB_GET_HOMEWORK, mapOf( + "idP" to data.registerId, + "idPD" to event.id + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + val homework = json.getJsonObject("praca") ?: return@webApiGet + + if (homework.getBoolean("zalacznik", false)) { + event.attachmentIds = mutableListOf(event.id) + event.attachmentNames = mutableListOf("Załącznik do zadania") + } + else { + event.attachmentIds = mutableListOf() + event.attachmentNames = mutableListOf() + } + event.homeworkBody = homework.getString("tresc") + + data.eventList.add(event) + data.eventListReplace = true + + EventBus.getDefault().postSticky(EventGetEvent(event)) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomeworkAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomeworkAttachment.kt new file mode 100644 index 00000000..f4a60f98 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetHomeworkAttachment.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-1. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.set +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class IdziennikWebGetHomeworkAttachment(override val data: DataIdziennik, + val owner: Any, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + const val TAG = "IdziennikWebGetHomeworkAttachment" + } + + init { + val homework = owner as Event + + /*val request = Request.Builder() + .url("") + .build() + data.app.http.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withThrowable(e)) + } + + override fun onResponse(call: Call, response: Response) { + val filename = response.header("content-disposition")?.substringAfter("\"")?.substringBeforeLast("\"") + + val file: File = File(Utils.getStorageDir(), filename) + val sink = file.sink().buffer() + response.body()?.source()?.let { + sink.writeAll(it) + } + sink.close() + } + })*/ + + webGet(TAG, IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT) { text -> + val hiddenFields = JsonObject() + Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach { + hiddenFields[it[1]] = it[2] + } + + webGetFile(TAG, IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT, Utils.getStorageDir(), mapOf( + "__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""), + "__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""), + "__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""), + "__EVENTTARGET" to "ctl00\$cphContent\$bt_pobraniePliku", + "ctl00\$dxComboUczniowie" to data.registerId, + "ctl00\$cphContent\$idPracyDomowej" to attachmentId + ), { file -> + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.ownerId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + homework.attachmentNames = mutableListOf(file.name) + data.eventList.add(homework) + data.eventListReplace = true + + EventBus.getDefault().postSticky(event) + onSuccess() + + }) { written, _ -> + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().postSticky(event) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt new file mode 100644 index 00000000..bd1808df --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-28 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_MESSAGE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebGetMessage(override val data: DataIdziennik, + private val message: MessageFull, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + const val TAG = "IdziennikWebGetMessage" + } + + init { data.profile?.also { profile -> + val metaPattern = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex() + val meta = metaPattern.find(message.body!!) + val messageIdString = meta?.get(1) ?: "" + + webApiGet(TAG, IDZIENNIK_WEB_GET_MESSAGE, parameters = mapOf( + "idWiadomosci" to messageIdString, + "typWiadomosci" to if (message.type == TYPE_SENT) 1 else 0 + )) { json -> + json.getJsonObject("d")?.getJsonObject("Wiadomosc")?.also { + val id = it.getLong("_recordId") + message.body = message.body?.replace(metaPattern, "[META:$messageIdString;$id]") + + message.clearAttachments() + it.getJsonArray("ListaZal")?.asJsonObjectList()?.forEach { attachment -> + message.addAttachment( + attachment.getLong("Id") ?: return@forEach, + attachment.getString("Nazwa") ?: return@forEach, + -1 + ) + } + + message.recipients?.clear() + when (message.type) { + TYPE_RECEIVED -> { + val recipientObject = MessageRecipientFull( + profileId = profileId, + id = -1, + messageId = message.id + ) + + val readDateString = it.getString("DataOdczytania") + recipientObject.readDate = if (readDateString.isNullOrBlank()) System.currentTimeMillis() + else Date.fromIso(readDateString) + + recipientObject.fullName = profile.accountName ?: profile.studentNameLong + + data.messageRecipientList.add(recipientObject) + message.addRecipient(recipientObject) + } + + TYPE_SENT -> { + it.getJsonArray("ListaOdbiorcow")?.asJsonObjectList()?.forEach { recipient -> + val recipientName = recipient.getString("NazwaOdbiorcy") ?: return@forEach + val teacher = data.getTeacherByLastFirst(recipientName) + + val recipientObject = MessageRecipientFull( + profileId = profileId, + id = teacher.id, + messageId = message.id + ) + + recipientObject.readDate = recipient.getLong("Status") ?: return@forEach + recipientObject.fullName = teacher.fullName + + data.messageRecipientList.add(recipientObject) + message.addRecipient(recipientObject) + } + } + } + + if (!message.seen) { + message.seen = true + + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + message.id, + message.seen, + message.notified + )) + } + + data.messageList.add(message) + data.messageListReplace = true + + EventBus.getDefault().postSticky(MessageGetEvent(message)) + onSuccess() + } + } + } ?: onSuccess() } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt new file mode 100644 index 00000000..a14efc24 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import androidx.room.OnConflictStrategy +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_RECIPIENT_LIST +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class IdziennikWebGetRecipientList(override val data: DataIdziennik, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebGetRecipientList" + } + + init { + webApiGet(TAG, IDZIENNIK_WEB_GET_RECIPIENT_LIST, mapOf( + "idP" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK_Pracownicy")?.asJsonObjectList()?.forEach { recipient -> + val name = recipient.getString("ImieNazwisko") ?: ": " + val (fullName, subject) = name.split(": ").let { + Pair(it.getOrNull(0), it.getOrNull(1)) + } + val guid = recipient.getString("Id") ?: "" + // get teacher by ID or create it + val teacher = data.getTeacherByFirstLast(fullName ?: " ") + teacher.loginId = guid + teacher.setTeacherType(Teacher.TYPE_TEACHER) + // unset OTHER that is automatically set in IdziennikApiMessages* + teacher.unsetTeacherType(Teacher.TYPE_OTHER) + teacher.typeDescription = subject + } + + json.getJsonArray("ListK_Opiekunowie")?.asJsonObjectList()?.forEach { recipient -> + val name = recipient.getString("ImieNazwisko") ?: ": " + val (fullName, parentOf) = Regexes.IDZIENNIK_MESSAGES_RECIPIENT_PARENT.find(name)?.let { + Pair(it.groupValues.getOrNull(1), it.groupValues.getOrNull(2)) + } ?: Pair(null, null) + val guid = recipient.getString("Id") ?: "" + // get teacher by ID or create it + val teacher = data.getTeacherByFirstLast(fullName ?: " ") + teacher.loginId = guid + teacher.setTeacherType(Teacher.TYPE_PARENT) + // unset OTHER that is automatically set in IdziennikApiMessages* + teacher.unsetTeacherType(Teacher.TYPE_OTHER) + teacher.typeDescription = parentOf + } + + val event = RecipientListGetEvent( + data.profileId, + data.teacherList.filter { it.loginId != null } + ) + + profile?.lastReceiversSync = System.currentTimeMillis() + + data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE + EventBus.getDefault().postSticky(event) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt similarity index 57% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebGrades.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt index e99387f6..90e17401 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt @@ -2,28 +2,32 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-28. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web import android.graphics.Color import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_GRADES -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikWebGrades(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { companion object { private const val TAG = "IdziennikWebGrades" } - init { + init { data.profile?.also { profile -> webApiGet(TAG, IDZIENNIK_WEB_GRADES, mapOf( "idPozDziennika" to data.registerId )) { result -> @@ -59,18 +63,24 @@ class IdziennikWebGrades(override val data: DataIdziennik, colorInt = Color.parseColor("#$gradeColor") } + val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val gradeObject = Grade( - profileId, - id, - category, - colorInt, - "", - name, - value, - weight, - semester, - teacher.id, - subject.id) + profileId = profileId, + id = id, + name = name, + type = TYPE_NORMAL, + value = value, + weight = weight, + color = colorInt, + category = category, + description = null, + comment = null, + semester = semester, + teacherId = teacher.id, + subjectId = subject.id, + addedDate = addedDate + ) when (grade.getInt("Typ")) { 0 -> { @@ -88,30 +98,39 @@ class IdziennikWebGrades(override val data: DataIdziennik, count += weight } - val historyObject = Grade( - profileId, - gradeObject.id * -1, - historyItem.get("Kategoria").asString, - Color.parseColor("#" + historyItem.get("Kolor").asString), - historyItem.get("Uzasadnienie").asString, - historyItem.get("Ocena").asString, - value, - if (value > 0f && countToTheAverage) weight * -1f else 0f, - historyItem.get("Semestr").asInt, - teacher.id, - subject.id) - historyObject.parentId = gradeObject.id + val historyColor = historyItem.getString("Kolor") ?: "" + colorInt = 0xff2196f3.toInt() + if (historyColor.isNotEmpty()) { + colorInt = Color.parseColor("#$historyColor") + } val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val historyObject = Grade( + profileId = profileId, + id = gradeObject.id * -1, + name = historyItem.getString("Ocena") ?: "", + type = TYPE_NORMAL, + value = value, + weight = if (value > 0f && countToTheAverage) weight * -1f else 0f, + color = colorInt, + category = historyItem.getString("Kategoria"), + description = historyItem.getString("Uzasadnienie"), + comment = null, + semester = historyItem.getInt("Semestr") ?: 1, + teacherId = teacher.id, + subjectId = subject.id, + addedDate = addedDate + ) + historyObject.parentId = gradeObject.id + data.gradeList.add(historyObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, historyObject.id, true, - true, - addedDate + true )) } // update the current grade's value with an average of all historical grades and itself @@ -123,33 +142,37 @@ class IdziennikWebGrades(override val data: DataIdziennik, } 1 -> { gradeObject.type = Grade.TYPE_SEMESTER1_FINAL - gradeObject.name = name + gradeObject.name = value.toInt().toString() gradeObject.weight = 0f } 2 -> { gradeObject.type = Grade.TYPE_YEAR_FINAL - gradeObject.name = name + gradeObject.name = value.toInt().toString() gradeObject.weight = 0f } } - val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add( Metadata( profileId, Metadata.TYPE_GRADE, id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile.empty, + data.profile.empty )) } } + data.toRemove.addAll(listOf( + Grade.TYPE_NORMAL, + Grade.TYPE_SEMESTER1_FINAL, + Grade.TYPE_YEAR_FINAL + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS) - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES) } - } + } ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt new file mode 100644 index 00000000..10b4a0c3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebHomework(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebHomework" + } + + init { + val param = JsonObject().apply { + addProperty("strona", 1) + addProperty("iloscNaStrone", 997) + addProperty("iloscRekordow", -1) + addProperty("kolumnaSort", "DataZadania") + addProperty("kierunekSort", 0) + addProperty("maxIloscZaznaczonych", 0) + addProperty("panelFiltrow", 0) + } + + webApiGet(TAG, IDZIENNIK_WEB_HOMEWORK, mapOf( + "idP" to data.registerId, + "data" to Date.getToday().stringY_m_d, + "wszystkie" to true, + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { homework -> + val id = homework.getLong("_recordId") ?: return@forEach + val eventDate = Date.fromY_m_d(homework.getString("dataO") ?: return@forEach) + val addedDate = Date.fromY_m_d(homework.getString("dataZ") ?: return@forEach) + val subjectName = homework.getString("przed") ?: return@forEach + val subjectId = data.getSubject(subjectName, null, subjectName).id + val teacherName = homework.getString("usr") ?: return@forEach + val teacherId = data.getTeacherByLastFirst(teacherName).id + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) + val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime + val topic = homework.getString("tytul")?.trim() ?: "" + + val seen = when (profile?.empty) { + true -> true + else -> eventDate < Date.getToday() + } + + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + eventObject.id, + seen, + seen + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_HOMEWORK) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt similarity index 51% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebNotices.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt index 7cdaf3af..64108bfd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt @@ -2,24 +2,29 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-28. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web -import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA -import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_NOTICES -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_NOTICES -import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.crc16 -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEGATIVE +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEUTRAL +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_POSITIVE +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikWebNotices(override val data: DataIdziennik, - val onSuccess: () -> Unit) : IdziennikWeb(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { companion object { private const val TAG = "IdziennikWebNotices" } @@ -51,25 +56,29 @@ class IdziennikWebNotices(override val data: DataIdziennik, } val noticeObject = Notice( - profileId, - noticeId, - jNotice.get("Tresc").asString, - jNotice.get("Semestr").asInt, - nType, - rTeacher.id) + profileId = profileId, + id = noticeId, + type = nType, + semester = jNotice.get("Semestr").asInt, + text = jNotice.getString("Tresc") ?: "", + category = null, + points = null, + teacherId = rTeacher.id, + addedDate = addedDate.inMillis + ) + data.noticeList.add(noticeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_NOTICES, SYNC_ALWAYS) - onSuccess() + onSuccess(ENDPOINT_IDZIENNIK_WEB_NOTICES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt new file mode 100644 index 00000000..b23e9f98 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_MISSING_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonArray +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString + +class IdziennikWebProposedGrades(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebProposedGrades" + } + + init { data.profile?.also { profile -> + webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf( + "idPozDziennika" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + val manager = data.app.gradesManager + + json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { subject -> + val subjectName = subject.getString("Przedmiot") ?: return@forEach + val subjectObject = data.getSubject(subjectName, null, subjectName) + + val semester1Proposed = subject.getString("OcenaSem1") ?: "" + val semester1Value = manager.getGradeValue(semester1Proposed) + val semester1Id = subjectObject.id * (-100) - 1 + val semester1Type = + if (semester1Value == 0f) TYPE_DESCRIPTIVE + else TYPE_SEMESTER1_PROPOSED + val semester1Name = when { + semester1Value == 0f -> " " + semester1Value % 1.0f == 0f -> semester1Value.toInt().toString() + else -> semester1Value.toString() + } + val semester1Color = + if (semester1Value == 0f) 0xff536dfe.toInt() + else -1 + + val semester2Proposed = subject.getString("OcenaSem2") ?: "" + val semester2Value = manager.getGradeValue(semester2Proposed) + val semester2Id = subjectObject.id * (-100) - 2 + val semester2Type = + if (semester2Value == 0f) TYPE_DESCRIPTIVE + else TYPE_YEAR_PROPOSED + val semester2Name = when { + semester2Value == 0f -> " " + semester2Value % 1.0f == 0f -> semester2Value.toInt().toString() + else -> semester2Value.toString() + } + val semester2Color = + if (semester2Value == 0f) 0xffff4081.toInt() + else -1 + + if (semester1Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester1Start.inMillis + else + System.currentTimeMillis() + + val gradeObject = Grade( + profileId = profileId, + id = semester1Id, + name = semester1Name, + type = semester1Type, + value = semester1Value, + weight = 0f, + color = semester1Color, + category = if (semester1Value == 0f) "Ocena opisowa semestralna" else null, + description = if (semester1Value == 0f) semester1Proposed else null, + comment = null, + semester = 1, + teacherId = -1, + subjectId = subjectObject.id, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + gradeObject.id, + profile.empty, + profile.empty + )) + } + + if (semester2Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester2Start.inMillis + else + System.currentTimeMillis() + + val gradeObject = Grade( + profileId = profileId, + id = semester2Id, + name = semester2Name, + type = semester2Type, + value = semester2Value, + weight = 0f, + color = semester2Color, + category = if (semester2Value == 0f) "Ocena opisowa końcoworoczna" else null, + description = if (semester2Value == 0f) semester2Proposed else null, + comment = null, + semester = 2, + teacherId = -1, + subjectId = subjectObject.id, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + gradeObject.id, + profile.empty, + profile.empty + )) + } + } + + data.toRemove.addAll(listOf(TYPE_SEMESTER1_PROPOSED, TYPE_YEAR_PROPOSED).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES) + } + } ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt new file mode 100644 index 00000000..343ebcac --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SEND_MESSAGE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent +import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import java.util.* + +class IdziennikWebSendMessage(override val data: DataIdziennik, + val recipients: List, + val subject: String, + val text: String, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebSendMessage" + } + + init { + val recipientsArray = JsonArray() + for (teacher in recipients) { + teacher.loginId?.let { + recipientsArray += it + } + } + + webApiGet(TAG, IDZIENNIK_WEB_SEND_MESSAGE, mapOf( + "Wiadomosc" to JsonObject( + "Tytul" to subject, + "Tresc" to text, + "Confirmation" to false, + "GuidMessage" to UUID.randomUUID().toString().toUpperCase(Locale.ROOT), + "Odbiorcy" to recipientsArray + ) + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + if (json.getBoolean("CzyJestBlad") != false) { + // TODO error + return@webApiGet + } + + IdziennikApiMessagesSent(data, null) { + val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } + val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } + val event = MessageSentEvent(data.profileId, message, message?.addedDate) + + EventBus.getDefault().postSticky(event) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt new file mode 100644 index 00000000..f225f177 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt @@ -0,0 +1,36 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOME +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString + +class IdziennikWebSwitchRegister(override val data: DataIdziennik, + val registerId: Int, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebSwitchRegister" + } + + init { + val hiddenFields = data.loginStore.getLoginData("hiddenFields", JsonObject()) + // TODO error checking + + webGet(TAG, IDZIENNIK_WEB_HOME, mapOf( + "__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""), + "__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""), + "__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""), + "ctl00\$dxComboUczniowie" to registerId + )) { text -> + Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let { + val registerId = it[1].toIntOrNull() ?: return@let + data.webSelectedRegister = registerId + } + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt new file mode 100644 index 00000000..e7d293c4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt @@ -0,0 +1,194 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import androidx.core.util.set +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class IdziennikWebTimetable(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebTimetable" + } + + init { data.profile?.also { profile -> + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d + + val weekStart = Date.fromY_m_d(getDate) + val weekEnd = weekStart.clone().stepForward(0, 0, 6) + + webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf( + "idPozDziennika" to data.registerId, + "pidRokSzkolny" to data.schoolYearId, + "data" to "${weekStart.stringY_m_d}T10:00:00.000Z" + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("GodzinyLekcyjne")?.asJsonObjectList()?.forEachIndexed { index, range -> + val lessonRange = LessonRange( + profileId, + index + 1, + range.getString("Poczatek")?.let { Time.fromH_m(it) } + ?: return@forEachIndexed, + range.getString("Koniec")?.let { Time.fromH_m(it) } ?: return@forEachIndexed + ) + data.lessonRanges[lessonRange.lessonNumber] = lessonRange + } + + val dates = mutableSetOf() + val lessons = mutableListOf() + + json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson -> + val subject = data.getSubject( + lesson.getString("Nazwa") ?: return@forEach, + lesson.getLong("Id"), + lesson.getString("Skrot") ?: "" + ) + val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") + ?: return@forEach) + + val newSubjectName = lesson.getString("PrzedmiotZastepujacy") + val newSubject = when (newSubjectName.isNullOrBlank()) { + true -> null + else -> data.getSubject(newSubjectName, null, newSubjectName) + } + + val newTeacherName = lesson.getString("NauZastepujacy") + val newTeacher = when (newTeacherName.isNullOrBlank()) { + true -> null + else -> data.getTeacherByFDotLast(newTeacherName) + } + + val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach + val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) + ?: return@forEach] + val lessonDate = weekStart.clone().stepForward(0, 0, weekDay) + val classroom = lesson.getString("NazwaSali") + + val type = lesson.getInt("TypZastepstwa") ?: -1 + + val lessonObject = Lesson(profileId, -1) + + when (type) { + 1, 2, 3, 4, 5 -> { + lessonObject.apply { + this.type = Lesson.TYPE_CHANGE + + this.date = lessonDate + this.lessonNumber = lessonRange.lessonNumber + this.startTime = lessonRange.startTime + this.endTime = lessonRange.endTime + this.subjectId = newSubject?.id + this.teacherId = newTeacher?.id + this.teamId = data.teamClass?.id + this.classroom = classroom + + this.oldDate = lessonDate + this.oldLessonNumber = lessonRange.lessonNumber + this.oldStartTime = lessonRange.startTime + this.oldEndTime = lessonRange.endTime + this.oldSubjectId = subject.id + this.oldTeacherId = teacher.id + this.oldTeamId = data.teamClass?.id + this.oldClassroom = classroom + } + } + 0 -> { + lessonObject.apply { + this.type = Lesson.TYPE_CANCELLED + + this.oldDate = lessonDate + this.oldLessonNumber = lessonRange.lessonNumber + this.oldStartTime = lessonRange.startTime + this.oldEndTime = lessonRange.endTime + this.oldSubjectId = subject.id + this.oldTeacherId = teacher.id + this.oldTeamId = data.teamClass?.id + this.oldClassroom = classroom + } + } + else -> { + lessonObject.apply { + this.type = Lesson.TYPE_NORMAL + + this.date = lessonDate + this.lessonNumber = lessonRange.lessonNumber + this.startTime = lessonRange.startTime + this.endTime = lessonRange.endTime + this.subjectId = subject.id + this.teacherId = teacher.id + this.teamId = data.teamClass?.id + this.classroom = classroom + } + } + } + + lessonObject.id = lessonObject.buildId() + + dates.add(lessonDate.value) + lessons.add(lessonObject) + + val seen = profile.empty || lessonDate < Date.getToday() + + if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LESSON_CHANGE, + lessonObject.id, + seen, + seen + )) + } + } + + val date: Date = weekStart.clone() + while (date <= weekEnd) { + if (!dates.contains(date.value)) { + lessons.add(Lesson(profileId, date.value.toLong()).apply { + this.type = Lesson.TYPE_NO_LESSONS + this.date = date.clone() + }) + } + + date.stepForward(0, 0, 1) + } + + d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") + + data.lessonList.addAll(lessons) + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_TIMETABLE) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt new file mode 100644 index 00000000..1a07c609 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-27. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SETTINGS +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.set +import pl.szczodrzynski.edziennik.swapFirstLastName + +class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikFirstLogin" + } + + private val web = IdziennikWeb(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_IDZIENNIK + var firstProfileId = loginStoreId + + IdziennikLoginWeb(data) { + web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text -> + //val accounts = json.getJsonArray("accounts") + + val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0" + val accountNameLong = if (isParent) + Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName() + else null + + var schoolYearStart: Int? = null + var schoolYearEnd: Int? = null + var schoolYearName: String? = null + val schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { + schoolYearName = it[2]+"/"+it[3] + schoolYearStart = it[2].toIntOrNull() + schoolYearEnd = it[3].toIntOrNull() + it[1].toIntOrNull() + } ?: run { + data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR) + .withApiResponse(text)) + return@webGet + } + + Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) + .toMutableList() + .reversed() + .forEach { match -> + val registerId = match[1].toIntOrNull() ?: return@forEach + val studentId = match[2] + val firstName = match[3] + val lastName = match[4] + val className = match[5] + " " + match[6] + + val studentNameLong = "$firstName $lastName".fixName() + val studentNameShort = "$firstName ${lastName[0]}.".fixName() + val accountName = if (accountNameLong == studentNameLong) null else accountNameLong + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.webUsername, + studentNameLong, + studentNameShort, + accountName + ).apply { + schoolYearStart?.let { studentSchoolYearStart = it } + studentClassName = className + studentData["studentId"] = studentId + studentData["registerId"] = registerId + studentData["schoolYearId"] = schoolYearId + } + profileList.add(profile) + } + + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt similarity index 85% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt index ff6adb6e..e1cef5c1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt @@ -2,12 +2,12 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-25. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.login +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik import pl.szczodrzynski.edziennik.utils.Utils class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt similarity index 71% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt index 4782b980..2afe481d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt @@ -2,9 +2,9 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-27. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.login +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) { companion object { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt similarity index 61% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt index 88729206..a2fd4d03 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt @@ -2,20 +2,19 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-26. */ -package pl.szczodrzynski.edziennik.api.v2.idziennik.login +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.TextCallbackHandler -import okhttp3.Cookie -import pl.szczodrzynski.edziennik.HOUR -import pl.szczodrzynski.edziennik.MINUTE -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.get -import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { companion object { @@ -24,22 +23,12 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { init { run { if (data.isWebLoginValid()) { - data.app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("ASP.NET_SessionId_iDziennik") - .value(data.webSessionId!!) - .domain("iuczniowie.progman.pl") - .secure().httpOnly().build(), - Cookie.Builder() - .name(".ASPXAUTH") - .value(data.webAuth!!) - .domain("iuczniowie.progman.pl") - .secure().httpOnly().build() - )) + data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId) + data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth) onSuccess() } else { - data.app.cookieJar.clearForDomain("iuczniowie.progman.pl") + data.app.cookieJar.clear("iuczniowie.progman.pl") if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) { loginWithCredentials() } @@ -62,13 +51,54 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { // login succeeded: there is a start page if (text.contains("czyWyswietlicDostepMobilny")) { - val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl") + val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl") run { - data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION - data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH - data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER + data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION + data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH + data.apiBearer = cookies["Bearer"]?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */ data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */ + + val hiddenFields = JsonObject() + Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach { + hiddenFields[it[1]] = it[2] + } + data.loginStore.putLoginData("hiddenFields", hiddenFields) + + Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let { + val registerId = it[1].toIntOrNull() ?: return@let + data.webSelectedRegister = registerId + } + + // for profiles created after archiving + data.schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { + it[1].toIntOrNull() + } ?: data.schoolYearId + data.profile?.studentClassName = Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) + .firstOrNull { it[1].toIntOrNull() == data.registerId } + ?.let { "${it[5]} ${it[6]}" } ?: data.profile?.studentClassName + + data.profile?.let { profile -> + Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also { + val number = it[1].toIntOrNull() ?: return@also + val luckyNumberObject = LuckyNumber( + profileId = data.profileId, + date = Date.getToday(), + number = number + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profile.id, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile.empty + )) + } + } + return@run null }?.let { errorCode -> data.error(ApiError(TAG, errorCode) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt similarity index 83% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/DataLibrus.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt index a18b4511..e77dd50d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/DataLibrus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt @@ -2,18 +2,17 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-21. */ -package pl.szczodrzynski.edziennik.api.v2.librus +package pl.szczodrzynski.edziennik.data.api.edziennik.librus -import okhttp3.Cookie import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_API -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_MESSAGES -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_PORTAL -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_SYNERGIA -import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.currentTimeUnix -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.isNotNullNorEmpty class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { @@ -31,26 +30,16 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app loginMethods += LOGIN_METHOD_LIBRUS_API if (isSynergiaLoginValid()) { loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA - app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("DZIENNIKSID") - .value(synergiaSessionId!!) - .domain("synergia.librus.pl") - .secure().httpOnly().build() - )) + app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId) } if (isMessagesLoginValid()) { loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES - app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("DZIENNIKSID") - .value(messagesSessionId!!) - .domain("wiadomosci.librus.pl") - .secure().httpOnly().build() - )) + app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId) } } + override fun generateUserCode() = "$schoolName:$apiLogin" + fun getColor(id: Int?): Int { return when (id) { 1 -> 0xFFF0E68C @@ -131,7 +120,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiLogin: String? = null var apiLogin: String? get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin } - set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value } + set(value) { profile?.putStudentData("accountLogin", value); mApiLogin = value } /** * A Synergia password. * Used: for login (API Login Method) in Synergia mode. @@ -140,7 +129,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiPassword: String? = null var apiPassword: String? get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword } - set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value } + set(value) { profile?.putStudentData("accountPassword", value); mApiPassword = value } /** * A JST login Code. @@ -148,16 +137,16 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app */ private var mApiCode: String? = null var apiCode: String? - get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode } - set(value) { profile?.putStudentData("accountCode", value) ?: return; mApiCode = value } + get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode } + set(value) { profile?.putStudentData("accountCode", value); mApiCode = value } /** * A JST login PIN. * Used only during first login in JST mode. */ private var mApiPin: String? = null var apiPin: String? - get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin } - set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value } + get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin } + set(value) { profile?.putStudentData("accountPin", value); mApiPin = value } /** * A Synergia API access token. @@ -168,7 +157,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiAccessToken: String? = null var apiAccessToken: String? get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken } - set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value } + set(value) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; } /** * A Synergia API refresh token. * Used when refreshing the [apiAccessToken] in JST, Synergia modes. @@ -176,7 +165,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiRefreshToken: String? = null var apiRefreshToken: String? get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken } - set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value } + set(value) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; } /** * The expiry time for [apiAccessToken], as a UNIX timestamp. * Used when refreshing the [apiAccessToken] in JST, Synergia modes. @@ -185,7 +174,17 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app private var mApiTokenExpiryTime: Long? = null var apiTokenExpiryTime: Long get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L } - set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value } + set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; } + + /** + * A push device ID, generated by Librus when registering + * a FCM token. I don't really know if this has any use, + * but it may be worthy to save that ID. + */ + private var mPushDeviceId: Int? = null + var pushDeviceId: Int + get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 } + set(value) { mPushDeviceId = value; profile?.putStudentData("pushDeviceId", value) ?: return; } /* _____ _ / ____| (_) @@ -256,6 +255,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app var startPointsSemester1: Int get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 } set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value } + private var mStartPointsSemester2: Int? = null var startPointsSemester2: Int get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 } @@ -265,8 +265,20 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app var enablePointGrades: Boolean get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true } set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value } + private var mEnableDescriptiveGrades: Boolean? = null var enableDescriptiveGrades: Boolean get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true } set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value } -} \ No newline at end of file + + private var mTimetableNotPublic: Boolean? = null + var timetableNotPublic: Boolean + get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false } + set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value } + + /** + * Set to false when Recaptcha helper doesn't provide a working token. + * When it's set to false uses Synergia for messages. + */ + var messagesLoginSuccessful: Boolean = true +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt similarity index 51% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index 626a4a33..abf3ac5f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -2,25 +2,30 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-21. */ -package pl.szczodrzynski.edziennik.api.v2.librus +package pl.szczodrzynski.edziennik.data.api.edziennik.librus import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback -import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData -import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetMessage -import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead -import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLogin -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusData +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.LibrusApiAnnouncementMarkAsRead +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetAttachment +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetRecipientList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesSendMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.utils.Utils.d class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { @@ -30,6 +35,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va val internalErrorList = mutableListOf() val data: DataLibrus + private var afterLogin: (() -> Unit)? = null init { data = DataLibrus(app, profile, loginStore).apply { @@ -40,9 +46,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun completed() { data.saveData() - data.notifyAndSyncEvents { - callback.onCompleted() - } + callback.onCompleted() } /* _______ _ _ _ _ _ @@ -53,18 +57,20 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| __/ | |__*/ - override fun sync(featureIds: List, viewId: Int?, arguments: JsonObject?) { + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { data.arguments = arguments - data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId) + data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId, onlyEndpoints) login() } - private fun login() { + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { d(TAG, "Trying to login with ${data.targetLoginMethodIds}") if (internalErrorList.isNotEmpty()) { d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } } + loginMethodId?.let { data.prepareFor(librusLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } LibrusLogin(data) { data() } @@ -76,39 +82,78 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va d(TAG, " - Internal errors:") internalErrorList.forEach { d(TAG, " - code $it") } } - LibrusData(data) { + afterLogin?.invoke() ?: LibrusData(data) { completed() } } override fun getMessage(message: MessageFull) { - LibrusLoginApi(data) { - LibrusLoginSynergia(data) { - LibrusLoginMessages(data) { - LibrusMessagesGetMessage(data, message) { - completed() - } - } + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + if (data.messagesLoginSuccessful) LibrusMessagesGetMessage(data, message) { completed() } + else LibrusSynergiaGetMessage(data, message) { completed() } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesSendMessage(data, recipients, subject, text) { + completed() } } } override fun markAllAnnouncementsAsRead() { - LibrusLoginApi(data) { - LibrusLoginSynergia(data) { - LibrusSynergiaMarkAllAnnouncementsAsRead(data) { - completed() - } + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaMarkAllAnnouncementsAsRead(data) { + completed() } } } - override fun firstLogin() { - LibrusFirstLogin(data) { - completed() + override fun getAnnouncement(announcement: AnnouncementFull) { + login(LOGIN_METHOD_LIBRUS_API) { + LibrusApiAnnouncementMarkAsRead(data, announcement) { + completed() + } } } + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { + when (owner) { + is Message -> { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + if (data.messagesLoginSuccessful) LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { completed() } + LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { completed() } + } + } + is EventFull -> { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaHomeworkGetAttachment(data, owner, attachmentId, attachmentName) { + completed() + } + } + } + else -> completed() + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesGetRecipientList(data) { + completed() + } + } + } + + override fun getEvent(eventFull: EventFull) { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaGetHomework(data, eventFull) { + completed() + } + } + } + + override fun firstLogin() { LibrusFirstLogin(data) { completed() } } override fun cancel() { d(TAG, "Cancelled") data.cancel() @@ -116,18 +161,9 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { - override fun onCompleted() { - callback.onCompleted() - } - - override fun onProgress(step: Float) { - callback.onProgress(step) - } - - override fun onStartProgress(stringRes: Int) { - callback.onStartProgress(stringRes) - } - + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { if (apiError.errorCode in internalErrorList) { // finish immediately if the same error occurs twice during the same sync @@ -138,35 +174,32 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va when (apiError.errorCode) { ERROR_LIBRUS_PORTAL_ACCESS_DENIED -> { data.loginMethods.remove(LOGIN_METHOD_LIBRUS_PORTAL) - data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_PORTAL) - data.targetLoginMethodIds.sort() + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_PORTAL) data.portalTokenExpiryTime = 0 login() } ERROR_LIBRUS_API_ACCESS_DENIED, ERROR_LIBRUS_API_TOKEN_EXPIRED -> { data.loginMethods.remove(LOGIN_METHOD_LIBRUS_API) - data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_API) - data.targetLoginMethodIds.sort() + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_API) data.apiTokenExpiryTime = 0 login() } ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED -> { data.loginMethods.remove(LOGIN_METHOD_LIBRUS_SYNERGIA) - data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_SYNERGIA) - data.targetLoginMethodIds.sort() + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_SYNERGIA) data.synergiaSessionIdExpiryTime = 0 login() } ERROR_LIBRUS_MESSAGES_ACCESS_DENIED -> { data.loginMethods.remove(LOGIN_METHOD_LIBRUS_MESSAGES) - data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_MESSAGES) - data.targetLoginMethodIds.sort() + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_MESSAGES) data.messagesSessionIdExpiryTime = 0 login() } ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING, + ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED, ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED, ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED -> { login() @@ -185,15 +218,19 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID -> { login() } - // TODO PORTAL CAPTCHA ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> { - loginStore.putLoginData("timetableNotPublic", true) + data.timetableNotPublic = true data() } ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE, ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> { data() } + ERROR_LIBRUS_API_DEVICE_REGISTERED -> { + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + data.profileId + data() + } else -> callback.onError(apiError) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/LibrusFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt similarity index 58% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/LibrusFeatures.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt index 79c7d2df..c14a00df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/LibrusFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt @@ -2,64 +2,71 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.librus +package pl.szczodrzynski.edziennik.data.api.edziennik.librus -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.models.Feature +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature -const val ENDPOINT_LIBRUS_API_ME = 1001 -const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002 -const val ENDPOINT_LIBRUS_API_CLASSES = 1003 -const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 1004 -const val ENDPOINT_LIBRUS_API_UNITS = 1005 -const val ENDPOINT_LIBRUS_API_USERS = 1006 -const val ENDPOINT_LIBRUS_API_SUBJECTS = 1007 -const val ENDPOINT_LIBRUS_API_CLASSROOMS = 1008 -const val ENDPOINT_LIBRUS_API_PUSH_CONFIG = 1010 -const val ENDPOINT_LIBRUS_API_TIMETABLES = 1015 -const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 1016 -const val ENDPOINT_LIBRUS_API_NORMAL_GC = 1021 -const val ENDPOINT_LIBRUS_API_POINT_GC = 1022 -const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023 -const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024 -const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025 -const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026 -const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 -const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 -const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 -const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 1034 -const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 1035 -const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 1036 -const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 1040 -const val ENDPOINT_LIBRUS_API_EVENTS = 1041 -const val ENDPOINT_LIBRUS_API_HOMEWORK = 1050 -const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 1060 -const val ENDPOINT_LIBRUS_API_NOTICE_TYPES = 1070 -const val ENDPOINT_LIBRUS_API_NOTICES = 1071 -const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 1080 -const val ENDPOINT_LIBRUS_API_ATTENDANCES = 1081 -const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 1090 -const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 1100 -const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES = 1109 -const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 1110 -const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 1120 -const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130 -const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010 -const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020 -const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030 -const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010 -const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020 -const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 -const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 3040 -const val ENDPOINT_LIBRUS_MESSAGES_GET = 3040 +const val ENDPOINT_LIBRUS_API_ME = 1001 +const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002 +const val ENDPOINT_LIBRUS_API_CLASSES = 1003 +const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 1004 +const val ENDPOINT_LIBRUS_API_UNITS = 1005 +const val ENDPOINT_LIBRUS_API_USERS = 1006 +const val ENDPOINT_LIBRUS_API_SUBJECTS = 1007 +const val ENDPOINT_LIBRUS_API_CLASSROOMS = 1008 +const val ENDPOINT_LIBRUS_API_LESSONS = 1009 +const val ENDPOINT_LIBRUS_API_PUSH_CONFIG = 1010 +const val ENDPOINT_LIBRUS_API_TIMETABLES = 1015 +const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 1016 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES = 1021 +const val ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES = 1022 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES = 1023 +const val ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES = 1024 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES = 1025 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES = 1026 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS = 1027 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 +const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 +const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 1034 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 1035 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 1036 +const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 1040 +const val ENDPOINT_LIBRUS_API_EVENTS = 1041 +const val ENDPOINT_LIBRUS_API_HOMEWORK = 1050 +const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 1060 +const val ENDPOINT_LIBRUS_API_NOTICE_TYPES = 1070 +const val ENDPOINT_LIBRUS_API_NOTICES = 1071 +const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 1080 +const val ENDPOINT_LIBRUS_API_ATTENDANCES = 1081 +const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 1090 +const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 1100 +const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES = 1109 +const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 1110 +const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 1120 +const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130 +const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010 +const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020 +const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED = 2040 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT = 2050 +const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010 +const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020 +const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 val LibrusFeatures = listOf( + Feature(LOGIN_TYPE_LIBRUS, FEATURE_ALWAYS_NEEDED, listOf( + ENDPOINT_LIBRUS_API_LESSONS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + // push config Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf( ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> - data.app.appConfig.fcmTokens[LOGIN_TYPE_LIBRUS]?.second?.contains(data.profileId) == false + (data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId) }, @@ -91,12 +98,15 @@ val LibrusFeatures = listOf( * All grades + categories. */ Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( - ENDPOINT_LIBRUS_API_NORMAL_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_POINT_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API, - ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + // Commented out, because TextGrades/Categories is the same as Grades/Categories + /* ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, */ + ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, @@ -108,11 +118,11 @@ val LibrusFeatures = listOf( * Homework - using API. * Sync only if account has premium access. */ - Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( + /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> (data as DataLibrus).isPremium - }, + },*/ /** * Behaviour - using API. */ @@ -219,9 +229,9 @@ val LibrusFeatures = listOf( */ Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA - ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)).withShouldSync { data -> + ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data -> !(data as DataLibrus).isPremium - }, + }*/, /** * Messages inbox - using messages website. diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt new file mode 100644 index 00000000..3652f18a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import android.content.Context +import android.webkit.WebView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.startCoroutineTimer +import kotlin.coroutines.CoroutineContext + +class LibrusRecaptchaHelper( + val context: Context, + url: String, + html: String, + val onSuccess: (url: String) -> Unit, + val onTimeout: () -> Unit +) : CoroutineScope { + companion object { + private const val TAG = "LibrusRecaptchaHelper" + } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + private val webView by lazy { + WebView(context).also { + it.settings.javaScriptEnabled = true + it.webViewClient = WebViewClient() + } + } + + private var timeout: Job? = null + private var timedOut = false + + inner class WebViewClient : android.webkit.WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + timeout?.cancel() + if (!timedOut) { + onSuccess(url) + } + return true + } + } + + init { + launch(Dispatchers.Main) { + webView.loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) + } + timeout = startCoroutineTimer(delayMillis = 10000L) { + timedOut = true + onTimeout() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt similarity index 77% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt index 837695d3..d06cc219 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt @@ -2,20 +2,20 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-21. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.JsonCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* -open class LibrusApi(open val data: DataLibrus) { +open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) { companion object { private const val TAG = "LibrusApi" } @@ -26,7 +26,7 @@ open class LibrusApi(open val data: DataLibrus) { val profile get() = data.profile - fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) { + fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, ignoreErrors: List = emptyList(), onSuccess: (json: JsonObject) -> Unit) { d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint") @@ -44,9 +44,12 @@ open class LibrusApi(open val data: DataLibrus) { .withResponse(response)) return } + /* +{"Status":"Error","Code":"DeviceRegistered","Message":"This device is alerdy registered.","Resources":{"..":{"Url":"https:\/\/api.librus.pl\/2.0\/Root"}},"Url":"https:\/\/api.librus.pl\/2.0\/ChangeRegister"}*/ val error = if (response?.code() == 200) null else json.getString("Code") ?: json.getString("Message") ?: + json.getString("Status") ?: response?.parserErrorBody error?.let { code -> when (code) { @@ -63,12 +66,17 @@ open class LibrusApi(open val data: DataLibrus) { "NotesIsNotActive" -> ERROR_LIBRUS_API_NOTES_NOT_ACTIVE "InvalidRequest" -> ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS "Nieprawidłowy węzeł." -> ERROR_LIBRUS_API_INCORRECT_ENDPOINT + "NoticeboardProblem" -> ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM + "DeviceRegistered" -> ERROR_LIBRUS_API_DEVICE_REGISTERED + "Maintenance" -> ERROR_LIBRUS_API_MAINTENANCE else -> ERROR_LIBRUS_API_OTHER }.let { errorCode -> - data.error(ApiError(tag, errorCode) - .withApiResponse(json) - .withResponse(response)) - return + if (errorCode !in ignoreErrors) { + data.error(ApiError(tag, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } } } @@ -112,8 +120,10 @@ open class LibrusApi(open val data: DataLibrus) { .allowErrorCode(HTTP_FORBIDDEN) .allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAVAILABLE) + .allowErrorCode(HTTP_NOT_FOUND) + .allowErrorCode(503) .callback(callback) .build() .enqueue() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt new file mode 100644 index 00000000..877d803b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt @@ -0,0 +1,231 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaGetMessages +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomework +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaInfo +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.utils.Utils + +class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LibrusEndpoints" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + /** + * API + */ + ENDPOINT_LIBRUS_API_ME -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + LibrusApiMe(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_SCHOOLS -> { + data.startProgress(R.string.edziennik_progress_endpoint_school_info) + LibrusApiSchools(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_CLASSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_classes) + LibrusApiClasses(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teams) + LibrusApiVirtualClasses(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_UNITS -> { + data.startProgress(R.string.edziennik_progress_endpoint_units) + LibrusApiUnits(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_USERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teachers) + LibrusApiUsers(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_SUBJECTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_subjects) + LibrusApiSubjects(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_CLASSROOMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_classrooms) + LibrusApiClassrooms(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_LESSONS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lessons) + LibrusApiLessons(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_PUSH_CONFIG -> { + data.startProgress(R.string.edziennik_progress_endpoint_push_config) + LibrusApiPushConfig(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TIMETABLES -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + LibrusApiTimetables(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiBehaviourGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiDescriptiveGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiTextGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiPointGradeCategories(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_comments) + LibrusApiGradeComments(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_comments) + LibrusApiBehaviourGradeComments(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + LibrusApiGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_behaviour_grades) + LibrusApiBehaviourGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_descriptive_grades) + LibrusApiDescriptiveGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEXT_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_descriptive_grades) + LibrusApiTextGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_POINT_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_point_grades) + LibrusApiPointGrades(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_EVENT_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_event_types) + LibrusApiEventTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_EVENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_events) + LibrusApiEvents(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + LibrusApiHomework(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_LUCKY_NUMBER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + LibrusApiLuckyNumber(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_NOTICE_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notice_types) + LibrusApiNoticeTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_NOTICES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notices) + LibrusApiNotices(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance_types) + LibrusApiAttendanceTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ATTENDANCES -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + LibrusApiAttendances(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ANNOUNCEMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_announcements) + LibrusApiAnnouncements(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_PT_MEETINGS -> { + data.startProgress(R.string.edziennik_progress_endpoint_pt_meetings) + LibrusApiPtMeetings(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_day_types) + LibrusApiTeacherFreeDayTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_days) + LibrusApiTeacherFreeDays(data, lastSync, onSuccess) + } + + /** + * SYNERGIA + */ + ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + LibrusSynergiaHomework(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_INFO -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + LibrusSynergiaInfo(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + } + + /** + * MESSAGES + */ + ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + } + ENDPOINT_LIBRUS_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + } + + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt new file mode 100644 index 00000000..dc8d8c47 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt @@ -0,0 +1,307 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import org.json.JSONObject +import org.json.XML +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.parser.Parser +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File +import java.io.StringWriter +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) { + companion object { + private const val TAG = "LibrusMessages" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun messagesGet(tag: String, module: String, method: Int = POST, + parameters: Map? = null, onSuccess: (doc: Document) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$module") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + when { + text.contains("Niepoprawny login i/lub hasło.") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN + text.contains("Nie odnaleziono wiadomości.") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND + text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED + text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE + text.contains("error") -> ERROR_LIBRUS_MESSAGES_ERROR + text.contains("eVarWhitThisNameNotExists") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("") -> ERROR_LIBRUS_MESSAGES_OTHER + else -> null + }?.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + try { + val doc = Jsoup.parse(text, "", Parser.xmlParser()) + onSuccess(doc) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) + + val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = docBuilder.newDocument() + val serviceElement = doc.createElement("service") + val headerElement = doc.createElement("header") + val dataElement = doc.createElement("data") + for ((key, value) in parameters.orEmpty()) { + val element = doc.createElement(key) + element.appendChild(doc.createTextNode(value.toString())) + dataElement.appendChild(element) + } + serviceElement.appendChild(headerElement) + serviceElement.appendChild(dataElement) + doc.appendChild(serviceElement) + val transformer = TransformerFactory.newInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") + val stringWriter = StringWriter() + transformer.transform(DOMSource(doc), StreamResult(stringWriter)) + val requestXml = stringWriter.toString() + + Request.builder() + .url("$LIBRUS_MESSAGES_URL/$module") + .userAgent(SYNERGIA_USER_AGENT) + .setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML) + .apply { + when (method) { + GET -> get() + POST -> post() + } + } + .callback(callback) + .build() + .enqueue() + } + + fun messagesGetJson(tag: String, module: String, method: Int = POST, + parameters: Map? = null, onSuccess: (json: JsonObject?) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$module") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + when { + text.contains("Niepoprawny login i/lub hasło.") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN + text.contains("Nie odnaleziono wiadomości.") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND + text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED + text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE + text.contains("error") -> ERROR_LIBRUS_MESSAGES_ERROR + text.contains("eVarWhitThisNameNotExists") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("") -> ERROR_LIBRUS_MESSAGES_OTHER + else -> null + }?.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + try { + val json: JSONObject? = XML.toJSONObject(text) + onSuccess(JsonParser().parse(json?.toString() ?: "{}")?.asJsonObject) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) + + val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = docBuilder.newDocument() + val serviceElement = doc.createElement("service") + val headerElement = doc.createElement("header") + val dataElement = doc.createElement("data") + for ((key, value) in parameters.orEmpty()) { + val element = doc.createElement(key) + element.appendChild(doc.createTextNode(value.toString())) + dataElement.appendChild(element) + } + serviceElement.appendChild(headerElement) + serviceElement.appendChild(dataElement) + doc.appendChild(serviceElement) + val transformer = TransformerFactory.newInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") + val stringWriter = StringWriter() + transformer.transform(DOMSource(doc), StreamResult(stringWriter)) + val requestXml = stringWriter.toString() + + Request.builder() + .url("$LIBRUS_MESSAGES_URL/$module") + .userAgent(SYNERGIA_USER_AGENT) + .setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML) + .apply { + when (method) { + GET -> get() + POST -> post() + } + } + .callback(callback) + .build() + .enqueue() + } + + fun sandboxGet(tag: String, action: String, parameters: Map? = null, + onSuccess: (json: JsonObject) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action") + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$LIBRUS_SANDBOX_URL$action") + .userAgent(SYNERGIA_USER_AGENT) + .apply { + parameters?.forEach { (k, v) -> + addParameter(k, v) + } + } + .post() + .callback(callback) + .build() + .enqueue() + } + + fun sandboxGetFile(tag: String, url: String, targetFile: File, onSuccess: (file: File) -> Unit, + method: Int = GET, + onProgress: (written: Long, total: Long) -> Unit) { + + d(tag, "Request: Librus/Messages - $url") + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(SYNERGIA_USER_AGENT) + .also { + when (method) { + POST -> it.post() + else -> it.get() + } + } + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt similarity index 92% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt index 97fdf33d..4a5e0af6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt @@ -1,12 +1,12 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.data +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.JsonCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection @@ -44,6 +44,7 @@ open class LibrusPortal(open val data: DataLibrus) { "Access token is invalid" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED "ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED "Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND + "Unable to refresh the account" -> ERROR_LIBRUS_PORTAL_MAINTENANCE else -> when (json.getString("hint")) { "Error while decoding to JSON" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED else -> ERROR_LIBRUS_PORTAL_OTHER @@ -97,8 +98,9 @@ open class LibrusPortal(open val data: DataLibrus) { .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) .allowErrorCode(HttpURLConnection.HTTP_GONE) + .allowErrorCode(424) .callback(callback) .build() .enqueue() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt similarity index 61% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusSynergia.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt index b112fa72..72faf801 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusSynergia.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt @@ -2,17 +2,17 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-21. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.TextCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.utils.Utils.d -open class LibrusSynergia(open val data: DataLibrus) { +open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) { companion object { private const val TAG = "LibrusSynergia" } @@ -29,22 +29,13 @@ open class LibrusSynergia(open val data: DataLibrus) { val callback = object : TextCallbackHandler() { override fun onSuccess(text: String?, response: Response?) { - val location = response?.headers()?.get("Location") - if (location?.endsWith("przerwa_techniczna") == true) { - // double checking for maintenance? - data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE) - .withApiResponse(text) - .withResponse(response)) - return - } - if (text.isNullOrEmpty()) { data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) .withResponse(response)) return } - if (!text.contains("jesteś zalogowany")) { + if (!text.contains("jesteś zalogowany") && !text.contains("Podgląd zadania")) { when { text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED text.contains("Przerwa techniczna") -> ERROR_LIBRUS_SYNERGIA_MAINTENANCE @@ -57,7 +48,6 @@ open class LibrusSynergia(open val data: DataLibrus) { } } - try { onSuccess(text) } catch (e: Exception) { @@ -99,4 +89,44 @@ open class LibrusSynergia(open val data: DataLibrus) { .build() .enqueue() } + + fun redirectUrlGet(tag: String, url: String, onSuccess: (url: String) -> Unit) { + d(tag, "Request: Librus/Synergia - $url") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response) { + val redirectUrl = response.headers().get("Location") + + if (redirectUrl != null) { + try { + onSuccess(redirectUrl) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_SYNERGIA_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } else { + data.error(ApiError(tag, ERROR_LIBRUS_SYNERGIA_OTHER) + .withResponse(response) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(LIBRUS_USER_AGENT) + .withClient(data.app.httpLazy) + .get() + .callback(callback) + .build() + .enqueue() + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt new file mode 100644 index 00000000..23cfceb4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-27 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS +import pl.szczodrzynski.edziennik.data.api.ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM +import pl.szczodrzynski.edziennik.data.api.POST +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull + +class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, + private val announcement: AnnouncementFull, + val onSuccess: () -> Unit +) : LibrusApi(data, null) { + companion object { + const val TAG = "LibrusApiAnnouncementMarkAsRead" + } + + init { + apiGet(TAG, "SchoolNotices/MarkAsRead/${announcement.idString}", method = POST, + ignoreErrors = listOf( + ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS, + ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM + )) { + announcement.seen = true + + EventBus.getDefault().postSticky(AnnouncementGetEvent(announcement)) + + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + announcement.id, + announcement.seen, + announcement.notified + )) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt new file mode 100644 index 00000000..d19be231 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiAnnouncements(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAnnouncements" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "SchoolNotices") { json -> + val announcements = json.getJsonArray("SchoolNotices")?.asJsonObjectList() + + announcements?.forEach { announcement -> + val longId = announcement.getString("Id") ?: return@forEach + val id = longId.crc32() + val subject = announcement.getString("Subject") ?: "" + val text = announcement.getString("Content") ?: "" + val startDate = Date.fromY_m_d(announcement.getString("StartDate")) + val endDate = Date.fromY_m_d(announcement.getString("EndDate")) + val teacherId = announcement.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val addedDate = announcement.getString("CreationDate")?.let { Date.fromIso(it) } + ?: System.currentTimeMillis() + val read = announcement.getBoolean("WasRead") ?: false + + val announcementObject = Announcement( + profileId = profileId, + id = id, + subject = subject, + text = text, + startDate = startDate, + endDate = endDate, + teacherId = teacherId, + addedDate = addedDate + ).also { + it.idString = longId + } + + data.announcementList.add(announcementObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + id, + read, + profile.empty || read + )) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt new file mode 100644 index 00000000..c620d13d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType + +class LibrusApiAttendanceTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAttendanceTypes" + } + + init { + apiGet(TAG, "Attendances/Types") { json -> + val attendanceTypes = json.getJsonArray("Types")?.asJsonObjectList() + + attendanceTypes?.forEach { attendanceType -> + val id = attendanceType.getLong("Id") ?: return@forEach + + val typeName = attendanceType.getString("Name") ?: "" + val typeSymbol = attendanceType.getString("Short") ?: "" + val typeColor = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } + + val isStandard = attendanceType.getBoolean("Standard") ?: false + val baseType = when (attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id) { + 1L -> Attendance.TYPE_ABSENT + 2L -> Attendance.TYPE_BELATED + 3L -> Attendance.TYPE_ABSENT_EXCUSED + 4L -> Attendance.TYPE_RELEASED + /*100*/else -> when (isStandard) { + true -> Attendance.TYPE_PRESENT + false -> Attendance.TYPE_PRESENT_CUSTOM + } + } + val typeShort = when (isStandard) { + true -> data.app.attendanceManager.getTypeShort(baseType) + false -> typeSymbol + } + + data.attendanceTypes.put(id, AttendanceType( + profileId, + id, + baseType, + typeName, + typeShort, + typeSymbol, + typeColor + )) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 2*DAY) + onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt new file mode 100644 index 00000000..7857dc2a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiAttendances(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAttendances" + } + + init { + if (data.attendanceTypes.isEmpty()) { + data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } + } + if (data.librusLessons.isEmpty()) { + data.db.librusLessonDao().getAllNow(profileId).toSparseArray(data.librusLessons) { it.lessonId } + } + + apiGet(TAG, "Attendances") { json -> + val attendances = json.getJsonArray("Attendances")?.asJsonObjectList() + + attendances?.forEach { attendance -> + val id = ((attendance.getString("Id") ?: return@forEach) + .replace("[^\\d.]".toRegex(), "")).toLong() + val lessonId = attendance.getJsonObject("Lesson")?.getLong("Id") ?: -1 + val lessonNo = attendance.getInt("LessonNo") ?: return@forEach + val lessonDate = Date.fromY_m_d(attendance.getString("Date")) + val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") + val semester = attendance.getInt("Semester") ?: return@forEach + + val typeId = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach + val type = data.attendanceTypes[typeId] ?: null + + val startTime = data.lessonRanges.get(lessonNo)?.startTime + + val lesson = if (lessonId != -1L) + data.librusLessons.singleOrNull { it.lessonId == lessonId } + else null + + val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) + + val attendanceObject = Attendance( + profileId = profileId, + id = id, + baseType = type?.baseType ?: Attendance.TYPE_UNKNOWN, + typeName = type?.typeName ?: "nieznany rodzaj", + typeShort = type?.typeShort ?: "?", + typeSymbol = type?.typeSymbol ?: "?", + typeColor = type?.typeColor, + date = lessonDate, + startTime = startTime, + semester = semester, + teacherId = teacherId ?: lesson?.teacherId ?: -1, + subjectId = lesson?.subjectId ?: -1, + addedDate = addedDate + ).also { + it.lessonNumber = lessonNo + } + + data.attendanceList.add(attendanceObject) + if(type?.baseType != Attendance.TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + id, + profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN, + profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN + )) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt new file mode 100644 index 00000000..650dfbb5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-3 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiBehaviourGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGradeCategories" + } + + init { + apiGet(TAG, "BehaviourGrades/Points/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val valueFrom = category.getFloat("ValueFrom") ?: 0f + val valueTo = category.getFloat("ValueTo") ?: 0f + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + Color.BLUE, + name + ).apply { + type = GradeCategory.TYPE_BEHAVIOUR + setValueRange(valueFrom, valueTo) + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES, 1 * WEEK) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt new file mode 100644 index 00000000..eedb1d1d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-7 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class LibrusApiBehaviourGradeComments(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGradeComments" + } + + init { + apiGet(TAG, "BehaviourGrades/Points/Comments") { json -> + + json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment -> + val id = comment.getLong("Id") ?: return@forEach + val text = comment.getString("Text")?.fixWhiteSpaces() ?: return@forEach + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + -1, + text + ).apply { + type = GradeCategory.TYPE_BEHAVIOUR_COMMENT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt new file mode 100644 index 00000000..fae79a50 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-3 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +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 java.text.DecimalFormat + +class LibrusApiBehaviourGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGrades" + } + + private val nameFormat by lazy { DecimalFormat("#.##") } + + private val types by lazy { + mapOf( + 1 to ("wz" to "wzorowe"), + 2 to ("bdb" to "bardzo dobre"), + 3 to ("db" to "dobre"), + 4 to ("popr" to "poprawne"), + 5 to ("ndp" to "nieodpowiednie"), + 6 to ("ng" to "naganne") + ) + } + + init { data.profile?.also { profile -> + apiGet(TAG, "BehaviourGrades/Points") { json -> + + if (data.startPointsSemester1 > 0) { + val semester1StartGradeObject = Grade( + profileId = profileId, + id = -101, + name = nameFormat.format(data.startPointsSemester1), + type = TYPE_POINT_SUM, + value = data.startPointsSemester1.toFloat(), + weight = 0f, + color = 0xffbdbdbd.toInt(), + category = data.app.getString(R.string.grade_start_points), + description = data.app.getString(R.string.grade_start_points_format, 1), + comment = null, + semester = 1, + teacherId = -1, + subjectId = 1, + addedDate = profile.getSemesterStart(1).inMillis + ) + + data.gradeList.add(semester1StartGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + semester1StartGradeObject.id, + true, + true + )) + } + + if (data.startPointsSemester2 > 0) { + val semester2StartGradeObject = Grade( + profileId = profileId, + id = -102, + name = nameFormat.format(data.startPointsSemester2), + type = TYPE_POINT_SUM, + value = data.startPointsSemester2.toFloat(), + weight = -1f, + color = 0xffbdbdbd.toInt(), + category = data.app.getString(R.string.grade_start_points), + description = data.app.getString(R.string.grade_start_points_format, 2), + comment = null, + semester = 2, + teacherId = -1, + subjectId = 1, + addedDate = profile.getSemesterStart(2).inMillis + ) + + data.gradeList.add(semester2StartGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + semester2StartGradeObject.id, + true, + true + )) + } + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val value = grade.getFloat("Value") + val shortName = grade.getString("ShortName") + val semester = grade.getInt("Semester") ?: profile.currentSemester + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val addedDate = grade.getString("AddDate")?.let { Date.fromIso(it) } + ?: System.currentTimeMillis() + + val text = grade.getString("Text") + val type = grade.getJsonObject("BehaviourGrade")?.getInt("Id")?.let { types[it] } + + val name = when { + type != null -> type.first + value != null -> (if (value > 0) "+" else "") + nameFormat.format(value) + shortName != null -> shortName + else -> return@forEach + } + + val color = data.getColor(when { + value == null || value == 0f -> 12 + value > 0 -> 16 + value < 0 -> 26 + else -> 12 + }) + + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1 + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_BEHAVIOUR + } + + val categoryName = category?.text ?: "" + + val comments = grade.getJsonArray("Comments") + ?.asJsonObjectList() + ?.mapNotNull { comment -> + val cId = comment.getLong("Id") ?: return@mapNotNull null + data.gradeCategories[cId]?.text + } ?: listOf() + + val description = listOfNotNull(type?.second) + comments + + val valueFrom = value ?: category?.valueFrom ?: 0f + val valueTo = category?.valueTo ?: 0f + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_POINT_SUM, + value = valueFrom, + weight = -1f, + color = color, + category = categoryName, + description = text ?: description.join(" - "), + comment = if (text != null) description.join(" - ") else null, + semester = semester, + teacherId = teacherId, + subjectId = 1, + addedDate = addedDate + ).apply { + valueMax = valueTo + } + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, Grade.TYPE_POINT_SUM)) + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClasses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt similarity index 74% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClasses.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt index d41206b4..171e80e1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClasses.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt @@ -2,20 +2,22 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-14 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.DAY -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_CLASSES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASSES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Team import pl.szczodrzynski.edziennik.getJsonObject import pl.szczodrzynski.edziennik.getLong import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date class LibrusApiClasses(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiClasses" } @@ -38,6 +40,8 @@ class LibrusApiClasses(override val data: DataLibrus, teacherId ) + data.profile?.studentClassName = name + data.teamList.put(id, teamObject) data.unitId = studentClass.getJsonObject("Unit").getLong("Id") ?: 0L @@ -53,7 +57,7 @@ class LibrusApiClasses(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSES, 4 * DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_CLASSES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClassrooms.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt similarity index 53% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClassrooms.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt index 662225e0..80a11105 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiClassrooms.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt @@ -2,32 +2,35 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-24. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_CLASSROOMS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.classrooms.Classroom +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASSROOMS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Classroom import java.util.* class LibrusApiClassrooms(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiClassrooms" } init { apiGet(TAG, "Classrooms") { json -> - val classrooms = json.getJsonArray("Classrooms").asJsonObjectList() + val classrooms = json.getJsonArray("Classrooms")?.asJsonObjectList() classrooms?.forEach { classroom -> val id = classroom.getLong("Id") ?: return@forEach val name = classroom.getString("Name")?.toLowerCase(Locale.getDefault()) ?: "" val symbol = classroom.getString("Symbol")?.toLowerCase(Locale.getDefault()) ?: "" - val nameShort = name.split(" ").onEach { it[0] }.joinToString() + val nameShort = name.fixWhiteSpaces().split(" ").onEach { it[0] }.joinToString() + val symbolParts = symbol.fixWhiteSpaces().split(" ") - val friendlyName = if (name != symbol && !name.contains(symbol) && !nameShort.contains(symbol)) { + val friendlyName = if (name != symbol && !name.contains(symbol) && !name.containsAll(symbolParts) && !nameShort.contains(symbol)) { classroom.getString("Symbol") + " " + classroom.getString("Name") } else { @@ -38,7 +41,7 @@ class LibrusApiClassrooms(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSROOMS, 4*DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_CLASSROOMS) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt new file mode 100644 index 00000000..94ce6972 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiDescriptiveGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiDescriptiveGradeCategories" + } + + init { + apiGet(TAG, "DescriptiveTextGrades/Skills") { json -> + json.getJsonArray("Skills")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + color, + name + ).apply { + type = GradeCategory.TYPE_DESCRIPTIVE + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt new file mode 100644 index 00000000..8cc4f1c1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE_TEXT +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_TEXT +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiDescriptiveGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiDescriptiveGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "BaseTextGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val description = grade.getString("Grade") + + val categoryId = grade.getJsonObject("Skill")?.getLong("Id") + ?: grade.getJsonObject("Category")?.getLong("Id") + ?: return@forEach + val type = when (grade.getJsonObject("Category")) { + null -> TYPE_DESCRIPTIVE_TEXT + else -> TYPE_TEXT + } + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == when (type) { + TYPE_DESCRIPTIVE_TEXT -> GradeCategory.TYPE_DESCRIPTIVE + else -> GradeCategory.TYPE_NORMAL + } + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = " ", + type = type, + value = 0f, + weight = 0f, + color = category?.color ?: -1, + category = category?.text, + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.addAll(listOf( + TYPE_DESCRIPTIVE_TEXT, + TYPE_TEXT + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + + data.setSyncNext(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEventTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt similarity index 52% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEventTypes.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt index 42fad5d2..331b8249 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiEventTypes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt @@ -2,23 +2,25 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-24. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENT_TYPES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.events.EventType +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENT_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.EventType class LibrusApiEventTypes(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiEventTypes" } init { apiGet(TAG, "HomeWorks/Categories") { json -> - val eventTypes = json.getJsonArray("Categories").asJsonObjectList() + val eventTypes = json.getJsonArray("Categories")?.asJsonObjectList() eventTypes?.forEach { eventType -> val id = eventType.getLong("Id") ?: return@forEach @@ -29,7 +31,7 @@ class LibrusApiEventTypes(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_EVENT_TYPES, 4*DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_EVENT_TYPES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt new file mode 100644 index 00000000..7a76f88e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +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 + +class LibrusApiEvents(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiEvents" + } + + init { + if (data.eventTypes.isEmpty()) { + data.db.eventTypeDao().getAllNow(profileId).toSparseArray(data.eventTypes) { it.id } + } + + apiGet(TAG, "HomeWorks") { json -> + val events = json.getJsonArray("HomeWorks")?.asJsonObjectList() + + events?.forEach { event -> + val id = event.getLong("Id") ?: return@forEach + val eventDate = Date.fromY_m_d(event.getString("Date")) + var topic = event.getString("Content")?.trim() ?: "" + val type = event.getJsonObject("Category")?.getLong("Id") ?: -1 + val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1 + val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1 + val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1 + + val lessonNo = event.getInt("LessonNo") + val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNo } + val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom")) + val addedDate = Date.fromIso(event.getString("AddDate")) + + event.getJsonObject("onlineLessonUrl")?.let { onlineLesson -> + val text = onlineLesson.getString("text")?.let { "$it - " } ?: "" + val url = onlineLesson.getString("url") + topic += "\n\n$text$url" + } + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = type, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId, + addedDate = addedDate + ) + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile?.empty ?: false, + profile?.empty ?: false + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureExceptTypes(listOf( + Event.TYPE_HOMEWORK, + Event.TYPE_PT_MEETING + ))) + + data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_EVENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt similarity index 57% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGradeCategories.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt index b91eae8a..518e643f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiGradeCategories.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt @@ -2,18 +2,20 @@ * Copyright (c) Kacper Ziubryniewicz 2019-11-5 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import android.graphics.Color import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GC -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS class LibrusApiGradeCategories(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiGradeCategories" } @@ -22,7 +24,7 @@ class LibrusApiGradeCategories(override val data: DataLibrus, apiGet(TAG, "Grades/Categories") { json -> json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> val id = category.getLong("Id") ?: return@forEach - val name = category.getString("Name") ?: "" + val name = category.getString("Name")?.fixWhiteSpaces() ?: "" val weight = when (category.getBoolean("CountToTheAverage")) { true -> category.getFloat("Weight") ?: 0f else -> 0f @@ -41,8 +43,8 @@ class LibrusApiGradeCategories(override val data: DataLibrus, data.gradeCategories.put(id, gradeCategoryObject) } - data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GC, SYNC_ALWAYS) - onSuccess() + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt new file mode 100644 index 00000000..971ac35b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-20 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class LibrusApiGradeComments(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiGradeComments" + } + + init { + apiGet(TAG, "Grades/Comments") { json -> + + json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment -> + val id = comment.getLong("Id") ?: return@forEach + val text = comment.getString("Text")?.fixWhiteSpaces() ?: return@forEach + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + -1, + text + ).apply { + type = GradeCategory.TYPE_NORMAL_COMMENT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt new file mode 100644 index 00000000..c2e2d0ce --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt @@ -0,0 +1,121 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "Grades") { json -> + val grades = json.getJsonArray("Grades")?.asJsonObjectList() + + grades?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1 + val name = grade.getString("Grade") ?: "" + val semester = grade.getInt("Semester") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: -1 + val addedDate = Date.fromIso(grade.getString("AddDate")) + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_NORMAL + } + + val value = Utils.getGradeValue(name) + val weight = if (name == "-" || name == "+" + || name.equals("np", ignoreCase = true) + || name.equals("bz", ignoreCase = true)) 0f + else category?.weight ?: 0f + + val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments -> + if (comments.isNotEmpty()) { + data.gradeCategories.singleOrNull { + it.type == GradeCategory.TYPE_NORMAL_COMMENT + && it.categoryId == comments[0].asJsonObject.getLong("Id") + }?.text + } else null + } ?: "" + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = when { + grade.getBoolean("IsConstituent") ?: false -> TYPE_NORMAL + grade.getBoolean("IsSemester") ?: false -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL + grade.getBoolean("IsSemesterProposition") ?: false -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED + grade.getBoolean("IsFinal") ?: false -> TYPE_YEAR_FINAL + grade.getBoolean("IsFinalProposition") ?: false -> TYPE_YEAR_PROPOSED + else -> TYPE_NORMAL + }, + value = value, + weight = weight, + color = category?.color ?: -1, + category = category?.text ?: "", + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId, + addedDate = addedDate + ) + + grade.getJsonObject("Improvement")?.also { + val historicalId = it.getLong("Id") + data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade -> + grade.parentId = gradeObject.id + if (grade.name == "nb") grade.weight = 0f + } + gradeObject.isImprovement = true + } + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_YEAR_FINAL, + TYPE_YEAR_PROPOSED + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt new file mode 100644 index 00000000..c1183c19 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-12. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiHomework(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiHomework" + } + + init { + apiGet(TAG, "HomeWorkAssignments") { json -> + val homeworkList = json.getJsonArray("HomeWorkAssignments")?.asJsonObjectList() + + homeworkList?.forEach { homework -> + val id = homework.getLong("Id") ?: return@forEach + val eventDate = Date.fromY_m_d(homework.getString("DueDate")) + val topic = homework.getString("Topic") + "\n" + homework.getString("Text") + val teacherId = homework.getJsonObject("Teacher")?.getLong("Id") ?: -1 + val addedDate = Date.fromY_m_d(homework.getString("Date")) + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = null, + topic = topic, + color = null, + type = -1, + teacherId = teacherId, + subjectId = -1, + teamId = -1, + addedDate = addedDate.inMillis + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + id, + profile?.empty ?: false, + profile?.empty ?: false + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_HOMEWORK) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt new file mode 100644 index 00000000..e32790c9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-6. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LESSONS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LibrusLesson + +class LibrusApiLessons(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiLessons" + } + + init { + apiGet(TAG, "Lessons") { json -> + val lessons = json.getJsonArray("Lessons")?.asJsonObjectList() + + lessons?.forEach { lesson -> + val id = lesson.getLong("Id") ?: return@forEach + val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id") ?: return@forEach + val subjectId = lesson.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val teamId = lesson.getJsonObject("Class")?.getLong("Id") + + val librusLesson = LibrusLesson( + profileId, + id, + teacherId, + subjectId, + teamId + ) + + data.librusLessons.put(id, librusLesson) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_LESSONS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_LESSONS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt similarity index 55% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiLuckyNumber.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt index 93579a02..80e6e299 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt @@ -2,30 +2,26 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-14 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api -import pl.szczodrzynski.edziennik.DAY -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.getInt -import pl.szczodrzynski.edziennik.getJsonObject -import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time class LibrusApiLuckyNumber(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiLuckyNumber" } init { - data.profile?.luckyNumber = -1 - data.profile?.luckyNumberDate = null - var nextSync = System.currentTimeMillis() + 2*DAY*1000 apiGet(TAG, "LuckyNumbers") { json -> @@ -37,14 +33,15 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, val luckyNumberDate = Date.fromY_m_d(luckyNumberEl.getString("LuckyNumberDay")) ?: Date.getToday() val luckyNumber = luckyNumberEl.getInt("LuckyNumber") ?: -1 val luckyNumberObject = LuckyNumber( - profileId, - luckyNumberDate, - luckyNumber + profileId = profileId, + date = luckyNumberDate, + number = luckyNumber ) - //if (luckyNumberDate > Date.getToday()) { + if (luckyNumberDate >= Date.getToday()) nextSync = luckyNumberDate.combineWith(Time(15, 0, 0)) - //} + else + nextSync = System.currentTimeMillis() + 6*HOUR*1000 data.luckyNumberList.add(luckyNumberObject) data.metadataList.add( @@ -52,15 +49,14 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, profileId, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + true, + profile?.empty ?: false )) } } data.setSyncNext(ENDPOINT_LIBRUS_API_LUCKY_NUMBER, syncAt = nextSync) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_LUCKY_NUMBER) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiMe.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiMe.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt index 8a5df3d0..9d5ce1d4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiMe.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt @@ -2,15 +2,17 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-3. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ME -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ME +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi class LibrusApiMe(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiMe" } @@ -24,7 +26,7 @@ class LibrusApiMe(override val data: DataLibrus, data.isPremium = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true val isParent = account?.getInt("GroupId") == 5 - data.profile?.accountNameLong = + data.profile?.accountName = if (isParent) buildFullName(account?.getString("FirstName"), account?.getString("LastName")) else null @@ -33,7 +35,7 @@ class LibrusApiMe(override val data: DataLibrus, buildFullName(user?.getString("FirstName"), user?.getString("LastName")) data.setSyncNext(ENDPOINT_LIBRUS_API_ME, 2*DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_ME) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt new file mode 100644 index 00000000..55e753ee --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.NoticeType + +class LibrusApiNoticeTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiNoticeTypes" + } + + init { + apiGet(TAG, "Notes/Categories") { json -> + val noticeTypes = json.getJsonArray("Categories")?.asJsonObjectList() + + noticeTypes?.forEach { noticeType -> + val id = noticeType.getLong("Id") ?: return@forEach + val name = noticeType.getString("CategoryName") ?: "" + + data.noticeTypes.put(id, NoticeType(profileId, id, name)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICE_TYPES, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_NOTICE_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt similarity index 60% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNotices.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt index 2f4ca5c9..ba531afe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt @@ -2,20 +2,22 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-24. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import androidx.core.util.isEmpty import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NOTICES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date class LibrusApiNotices(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiNotices" } @@ -26,7 +28,7 @@ class LibrusApiNotices(override val data: DataLibrus, } apiGet(TAG, "Notes") { json -> - val notes = json.getJsonArray("Notes").asJsonObjectList() + val notes = json.getJsonArray("Notes")?.asJsonObjectList() notes?.forEach { note -> val id = note.getLong("Id") ?: return@forEach @@ -44,12 +46,15 @@ class LibrusApiNotices(override val data: DataLibrus, val semester = profile?.dateToSemester(addedDate) ?: 1 val noticeObject = Notice( - profileId, - id, - categoryText+"\n"+text, - semester, - type, - teacherId + profileId = profileId, + id = id, + type = type, + semester = semester, + text = text, + category = categoryText, + points = null, + teacherId = teacherId, + addedDate = addedDate.inMillis ) data.noticeList.add(noticeObject) @@ -59,13 +64,12 @@ class LibrusApiNotices(override val data: DataLibrus, Metadata.TYPE_NOTICE, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICES, SYNC_ALWAYS) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_NOTICES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt new file mode 100644 index 00000000..bba6ead9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiPointGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPointGradeCategories" + } + + init { + apiGet(TAG, "PointGrades/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + val countToAverage = category.getBoolean("CountToTheAverage") ?: true + val weight = if (countToAverage) category.getFloat("Weight") ?: 0f else 0f + val valueFrom = category.getFloat("ValueFrom") ?: 0f + val valueTo = category.getFloat("ValueTo") ?: 0f + + val gradeCategoryObject = GradeCategory( + profileId, + id, + weight, + color, + name + ).apply { + type = GradeCategory.TYPE_POINT + setValueRange(valueFrom, valueTo) + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt new file mode 100644 index 00000000..9da2c435 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiPointGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPointGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "PointGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val name = grade.getString("Grade") ?: return@forEach + val value = grade.getFloat("GradeValue") ?: 0f + + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: return@forEach + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_POINT + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_POINT_AVG, + value = value, + weight = category?.weight ?: 0f, + color = category?.color ?: -1, + category = category?.text ?: "", + description = null, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId, + addedDate = addedDate + ).apply { + valueMax = category?.valueTo ?: 0f + } + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, TYPE_POINT_AVG)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_POINT_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiPtMeetings.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt similarity index 54% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiPtMeetings.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt index 2c4e5f53..6c0a9dd7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiPtMeetings.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt @@ -2,26 +2,29 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-24. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time class LibrusApiPtMeetings(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiPtMeetings" } init { apiGet(TAG, "ParentTeacherConferences") { json -> - val ptMeetings = json.getJsonArray("ParentTeacherConferences").asJsonObjectList() + val ptMeetings = json.getJsonArray("ParentTeacherConferences")?.asJsonObjectList() ptMeetings?.forEach { meeting -> val id = meeting.getLong("Id") ?: return@forEach @@ -36,17 +39,16 @@ class LibrusApiPtMeetings(override val data: DataLibrus, } val eventObject = Event( - profileId, - id, - eventDate, - startTime, - topic, - -1, - Event.TYPE_PT_MEETING, - false, - teacherId, - -1, - data.teamClass?.id ?: -1 + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_PT_MEETING, + teacherId = teacherId, + subjectId = -1, + teamId = data.teamClass?.id ?: -1 ) data.eventList.add(eventObject) @@ -56,13 +58,14 @@ class LibrusApiPtMeetings(override val data: DataLibrus, Metadata.TYPE_EVENT, id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_PT_MEETING)) + data.setSyncNext(ENDPOINT_LIBRUS_API_PT_MEETINGS, 12*HOUR) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_PT_MEETINGS) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt new file mode 100644 index 00000000..9073d03f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.JsonObject +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PUSH_CONFIG +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject + +class LibrusApiPushConfig(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPushConfig" + } + + init { data.app.config.sync.tokenLibrus?.also { tokenLibrus -> + if(tokenLibrus.isEmpty()) { + data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS) + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + profileId + onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) + return@also + } + + apiGet(TAG, "ChangeRegister", payload = JsonObject( + "provider" to "FCM", + "device" to tokenLibrus, + "sendPush" to "1", + "appVersion" to 4 + )) { json -> + json.getJsonObject("ChangeRegister")?.getInt("Id")?.let { data.pushDeviceId = it } + + // sync always: this endpoint has .shouldSync set + data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS) + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + profileId + onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSchools.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt similarity index 72% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSchools.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt index 5b1b1086..d405f9bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSchools.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt @@ -2,18 +2,20 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-4. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_SCHOOLS -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SCHOOLS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange import pl.szczodrzynski.edziennik.utils.models.Time import java.util.* class LibrusApiSchools(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiSchools" } @@ -26,12 +28,7 @@ class LibrusApiSchools(override val data: DataLibrus, // create the school's short name using first letters of each long name's word // append the town name and save to student data - var schoolNameShort = "" - schoolNameLong?.split(" ")?.forEach { - if (it.isBlank()) - return@forEach - schoolNameShort += it[0].toLowerCase() - } + val schoolNameShort = schoolNameLong?.firstLettersName val schoolTown = school?.getString("Town")?.toLowerCase(Locale.getDefault()) data.schoolName = schoolId.toString() + schoolNameShort + "_" + schoolTown @@ -53,7 +50,7 @@ class LibrusApiSchools(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_SCHOOLS, 4 * DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_SCHOOLS) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSubjects.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt similarity index 54% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSubjects.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt index 2cfd9be2..cfd9bd31 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiSubjects.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt @@ -2,23 +2,25 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-23. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_SUBJECTS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SUBJECTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Subject class LibrusApiSubjects(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiSubjects" } init { apiGet(TAG, "Subjects") { json -> - val subjects = json.getJsonArray("Subjects").asJsonObjectList() + val subjects = json.getJsonArray("Subjects")?.asJsonObjectList() subjects?.forEach { subject -> val id = subject.getLong("Id") ?: return@forEach @@ -31,7 +33,7 @@ class LibrusApiSubjects(override val data: DataLibrus, data.subjectList.put(1, Subject(profileId, 1, "Zachowanie", "zach")) data.setSyncNext(ENDPOINT_LIBRUS_API_SUBJECTS, 4*DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_SUBJECTS) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDayTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt similarity index 59% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDayTypes.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt index eb82bbe2..e09504bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDayTypes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt @@ -2,23 +2,25 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-19 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceType +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsenceType class LibrusApiTeacherFreeDayTypes(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiTeacherFreeDayTypes" } init { apiGet(TAG, "TeacherFreeDays/Types") { json -> - val teacherAbsenceTypes = json.getJsonArray("Types").asJsonObjectList() + val teacherAbsenceTypes = json.getJsonArray("Types")?.asJsonObjectList() teacherAbsenceTypes?.forEach { teacherAbsenceType -> val id = teacherAbsenceType.getLong("Id") ?: return@forEach @@ -34,7 +36,7 @@ class LibrusApiTeacherFreeDayTypes(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES, 7 * DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDays.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt similarity index 63% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDays.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt index 649e3377..973ada46 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTeacherFreeDays.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt @@ -2,21 +2,23 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-4. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import androidx.core.util.isEmpty import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsence +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time class LibrusApiTeacherFreeDays(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiTeacherFreeDays" } @@ -27,7 +29,7 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, } apiGet(TAG, "TeacherFreeDays") { json -> - val teacherAbsences = json.getJsonArray("TeacherFreeDays").asJsonObjectList() + val teacherAbsences = json.getJsonArray("TeacherFreeDays")?.asJsonObjectList() teacherAbsences?.forEach { teacherAbsence -> val id = teacherAbsence.getLong("Id") ?: return@forEach @@ -41,15 +43,15 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, val timeTo = teacherAbsence.getString("TimeTo")?.let { Time.fromH_m_s(it) } val teacherAbsenceObject = TeacherAbsence( - profileId, - id, - teacherId, - type, - name, - dateFrom, - dateTo, - timeFrom, - timeTo + profileId = profileId, + id = id, + type = type, + name = name, + dateFrom = dateFrom, + dateTo = dateTo, + timeFrom = timeFrom, + timeTo = timeTo, + teacherId = teacherId ) data.teacherAbsenceList.add(teacherAbsenceObject) @@ -57,14 +59,13 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, profileId, Metadata.TYPE_TEACHER_ABSENCE, id, - profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + true, + profile?.empty ?: false )) } data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS, 6*HOUR, DRAWER_ITEM_AGENDA) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt new file mode 100644 index 00000000..9fd6afee --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi + +class LibrusApiTemplate(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApi" + } + + init { + /*apiGet(TAG, "") { json -> + + data.setSyncNext(ENDPOINT_LIBRUS_API_, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_) + }*/ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt new file mode 100644 index 00000000..45b6b069 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiTextGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTextGradeCategories" + } + + init { + apiGet(TAG, "TextGrades/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + color, + name + ).apply { + type = GradeCategory.TYPE_TEXT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt new file mode 100644 index 00000000..7b7c8c6f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiTextGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTextGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "DescriptiveGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + + val map = grade.getString("Map") + val realValue = grade.getString("RealGradeValue") + + val name = map ?: realValue ?: return@forEach + val description = if (map != null && map != realValue) realValue ?: "" else "" + + val categoryId = grade.getJsonObject("Skill")?.getLong("Id") ?: return@forEach + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_DESCRIPTIVE + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_DESCRIPTIVE, + value = 0f, + weight = 0f, + color = category?.color ?: -1, + category = category?.text ?: "", + description = description, + comment = grade.getString("Phrase") /* whatever it is */, + semester = semester, + teacherId = teacherId, + subjectId = subjectId, + addedDate = addedDate + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, TYPE_DESCRIPTIVE)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEXT_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTimetables.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt similarity index 81% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTimetables.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt index 15865a2f..9a463a6b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiTimetables.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt @@ -2,24 +2,27 @@ * Copyright (c) Kuba Szczodrzyński 2019-11-10. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import androidx.core.util.isEmpty import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TIMETABLES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +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.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week class LibrusApiTimetables(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiTimetables" } @@ -29,9 +32,18 @@ class LibrusApiTimetables(override val data: DataLibrus, data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id } } - val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) } + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d - apiGet(TAG, "Timetables?weekStart=$getDate") { json -> + + val weekStart = Date.fromY_m_d(getDate) + val weekEnd = weekStart.clone().stepForward(0, 0, 6) + + apiGet(TAG, "Timetables?weekStart=${weekStart.stringY_m_d}") { json -> val days = json.getJsonObject("Timetable") days?.entrySet()?.forEach { (dateString, dayEl) -> @@ -50,24 +62,24 @@ class LibrusApiTimetables(override val data: DataLibrus, } if (day.isNullOrEmpty() || !lessonsFound) { - data.lessonNewList.add(Lesson(profileId, lessonDate.value.toLong()).apply { + data.lessonList.add(Lesson(profileId, lessonDate.value.toLong()).apply { type = Lesson.TYPE_NO_LESSONS date = lessonDate }) } } - val weekStart = Date.fromY_m_d(getDate) - val weekEnd = weekStart.clone().stepForward(0, 0, 6) d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") + if (data.timetableNotPublic) data.timetableNotPublic = false + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_TIMETABLES) } } - private fun parseLesson(lessonDate: Date, lesson: JsonObject) { + private fun parseLesson(lessonDate: Date, lesson: JsonObject) { data.profile?.also { profile -> val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false val isCancelled = lesson.getBoolean("IsCanceled") ?: false @@ -80,8 +92,7 @@ class LibrusApiTimetables(override val data: DataLibrus, val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id") val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId - val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF) - val lessonObject = Lesson(profileId, id) + val lessonObject = Lesson(profileId, -1) if (isSubstitution && isCancelled) { // shifted lesson - source @@ -176,17 +187,20 @@ class LibrusApiTimetables(override val data: DataLibrus, } } + lessonObject.id = lessonObject.buildId() + + val seen = profile.empty || lessonDate < Date.getToday() + if (lessonObject.type != Lesson.TYPE_NORMAL) { data.metadataList.add( Metadata( - data.profileId, + profileId, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, - data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + seen, + seen )) } - data.lessonNewList.add(lessonObject) - } + data.lessonList.add(lessonObject) + }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUnits.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt similarity index 66% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUnits.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt index 42353ad5..791283ed 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiUnits.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt @@ -2,15 +2,17 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-23. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_UNITS -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_UNITS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi class LibrusApiUnits(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiUnits" } @@ -18,12 +20,12 @@ class LibrusApiUnits(override val data: DataLibrus, init { run { if (data.unitId == 0L) { data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 12 * DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_UNITS) return@run } apiGet(TAG, "Units") { json -> - val units = json.getJsonArray("Units").asJsonObjectList() + val units = json.getJsonArray("Units")?.asJsonObjectList() units?.singleOrNull { it.getLong("Id") == data.unitId }?.also { unit -> val startPoints = unit.getJsonObject("BehaviourGradesSettings")?.getJsonObject("StartPoints") @@ -38,7 +40,7 @@ class LibrusApiUnits(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 7 * DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_UNITS) } }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt new file mode 100644 index 00000000..0ac9a5ea --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-23. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_USERS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class LibrusApiUsers(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiUsers" + } + + init { + apiGet(TAG, "Users") { json -> + val users = json.getJsonArray("Users")?.asJsonObjectList() + + users?.forEach { user -> + val id = user.getLong("Id") ?: return@forEach + val firstName = user.getString("FirstName")?.fixName() ?: "" + val lastName = user.getString("LastName")?.fixName() ?: "" + + val teacher = Teacher(profileId, id, firstName, lastName) + + if (user.getBoolean("IsSchoolAdministrator") == true) + teacher.setTeacherType(Teacher.TYPE_SCHOOL_ADMIN) + if (user.getBoolean("IsPedagogue") == true) + teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE) + + data.teacherList.put(id, teacher) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_USERS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_USERS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiVirtualClasses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt similarity index 59% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiVirtualClasses.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt index 3cc1559e..9937f808 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/api/LibrusApiVirtualClasses.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt @@ -2,23 +2,25 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-23. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Team class LibrusApiVirtualClasses(override val data: DataLibrus, - val onSuccess: () -> Unit) : LibrusApi(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { companion object { const val TAG = "LibrusApiVirtualClasses" } init { apiGet(TAG, "VirtualClasses") { json -> - val virtualClasses = json.getJsonArray("VirtualClasses").asJsonObjectList() + val virtualClasses = json.getJsonArray("VirtualClasses")?.asJsonObjectList() virtualClasses?.forEach { virtualClass -> val id = virtualClass.getLong("Id") ?: return@forEach @@ -30,7 +32,7 @@ class LibrusApiVirtualClasses(override val data: DataLibrus, } data.setSyncNext(ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES, 4*DAY) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt new file mode 100644 index 00000000..b54e704b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.db.entity.Message +import kotlin.coroutines.CoroutineContext + +class LibrusMessagesGetAttachment(override val data: DataLibrus, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusMessages(data, null), CoroutineScope { + companion object { + const val TAG = "LibrusMessagesGetAttachment" + } + + private var job = Job() + + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + init { + messagesGet(TAG, "GetFileDownloadLink", parameters = mapOf( + "fileId" to attachmentId, + "msgId" to message.id, + "archive" to 0 + )) { doc -> + val downloadLink = doc.select("response GetFileDownloadLink downloadLink").text() + + LibrusSandboxDownloadAttachment(data, downloadLink, message, attachmentId, attachmentName, onSuccess) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt similarity index 65% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetList.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt index 3aee398d..9f962bf9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetList.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt @@ -2,27 +2,28 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-24 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.messages +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages import pl.szczodrzynski.edziennik.DAY import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES -import pl.szczodrzynski.edziennik.api.v2.ERROR_NOT_IMPLEMENTED -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_SENT -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher +import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.models.Date -class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = TYPE_RECEIVED, - archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) { +class LibrusMessagesGetList(override val data: DataLibrus, + override val lastSync: Long?, + private val type: Int = TYPE_RECEIVED, + archived: Boolean = false, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusMessages(data, lastSync) { companion object { const val TAG = "LibrusMessagesGetList" } @@ -33,6 +34,10 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int Message.TYPE_SENT -> "Outbox/action/GetList" else -> null } + val endpointId = when (type) { + TYPE_RECEIVED -> ENDPOINT_LIBRUS_MESSAGES_RECEIVED + else -> ENDPOINT_LIBRUS_MESSAGES_SENT + } if (endpoint != null) { messagesGet(TAG, endpoint, parameters = mapOf( @@ -51,12 +56,12 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int val recipientFirstName = element.select(when (type) { TYPE_RECEIVED -> "senderFirstName" else -> "receiverFirstName" - }).text().trim() + }).text().fixName() val recipientLastName = element.select(when (type) { TYPE_RECEIVED -> "senderLastName" else -> "receiverLastName" - }).text().trim() + }).text().fixName() val recipientId = data.teacherList.singleOrNull { it.name == recipientFirstName && it.surname == recipientLastName @@ -73,7 +78,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int val senderId = when (type) { TYPE_RECEIVED -> recipientId - else -> -1 + else -> null } val receiverId = when (type) { @@ -87,13 +92,13 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int } val messageObject = Message( - profileId, - id, - subject, - null, - type, - senderId, - -1 + profileId = profileId, + id = id, + type = type, + subject = subject, + body = null, + senderId = senderId, + addedDate = sentDate ) val messageRecipientObject = MessageRecipient( @@ -104,15 +109,19 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int id ) - data.messageIgnoreList.add(messageObject) + element.select("isAnyFileAttached")?.text()?.let { + if (it == "1") + messageObject.hasAttachments = true + } + + data.messageList.add(messageObject) data.messageRecipientList.add(messageRecipientObject) - data.metadataList.add(Metadata( + data.setSeenMetadataList.add(Metadata( profileId, Metadata.TYPE_MESSAGE, id, notified, - notified, - sentDate + notified )) } @@ -120,11 +129,11 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) } - onSuccess() + onSuccess(endpointId) } } else { data.error(TAG, ERROR_NOT_IMPLEMENTED) - onSuccess() + onSuccess(endpointId) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt similarity index 50% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetMessage.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt index 83474d02..49ee0d52 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/messages/LibrusMessagesGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt @@ -2,28 +2,30 @@ * Copyright (c) Kacper Ziubryniewicz 2019-11-11 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.messages +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages import android.util.Base64 import org.greenrobot.eventbus.EventBus -import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED -import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull -import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.notEmptyOrNull import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.utils.models.Date import java.nio.charset.Charset -class LibrusMessagesGetMessage( - override val data: DataLibrus, - private val messageObject: MessageFull, - val onSuccess: () -> Unit -) : LibrusMessages(data) { +class LibrusMessagesGetMessage(override val data: DataLibrus, + private val messageObject: MessageFull, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { companion object { const val TAG = "LibrusMessagesGetMessage" } @@ -38,7 +40,7 @@ class LibrusMessagesGetMessage( val body = Base64.decode(message.select("Message").text(), Base64.DEFAULT) .toString(Charset.defaultCharset()) .replace("\n", "
    ") - .replace("", "") messageObject.apply { @@ -56,24 +58,57 @@ class LibrusMessagesGetMessage( when (messageObject.type) { TYPE_RECEIVED -> { - val senderLoginId = message.select("senderId").text() - data.teacherList.singleOrNull { it.id == messageObject.senderId }?.loginId = senderLoginId + val senderLoginId = message.select("senderId").text().notEmptyOrNull() + val senderGroupId = message.select("senderGroupId").text().toIntOrNull() + val userClass = message.select("userClass").text().notEmptyOrNull() + data.teacherList.singleOrNull { it.id == messageObject.senderId }?.apply { + loginId = senderLoginId + setTeacherType(when (senderGroupId) { + /* https://api.librus.pl/2.0/Messages/Role */ + 0, 1, 99 -> Teacher.TYPE_SUPER_ADMIN + 2 -> Teacher.TYPE_SCHOOL_ADMIN + 3 -> Teacher.TYPE_PRINCIPAL + 4 -> Teacher.TYPE_TEACHER + 5, 9 -> { + if (typeDescription == null) + typeDescription = userClass + Teacher.TYPE_PARENT + } + 7 -> Teacher.TYPE_SECRETARIAT + 8 -> { + if (typeDescription == null) + typeDescription = userClass + Teacher.TYPE_STUDENT + } + 10 -> Teacher.TYPE_PEDAGOGUE + 11 -> Teacher.TYPE_LIBRARIAN + 12 -> Teacher.TYPE_SPECIALIST + 21 -> { + typeDescription = "Jednostka Nadrzędna" + Teacher.TYPE_OTHER + } + 50 -> { + typeDescription = "Jednostka Samorządu Terytorialnego" + Teacher.TYPE_OTHER + } + else -> Teacher.TYPE_OTHER + }) + } val readDateText = message.select("readDate").text() - val readDate = when (readDateText.isNotEmpty()) { + val readDate = when (readDateText.isNotNullNorEmpty()) { true -> Date.fromIso(readDateText) else -> 0 } val messageRecipientObject = MessageRecipientFull( - profileId, - -1, - -1, - readDate, - messageObject.id + profileId = profileId, + id = -1, + messageId = messageObject.id, + readDate = readDate ) - messageRecipientObject.fullName = profile.accountNameLong ?: profile.studentNameLong + messageRecipientObject.fullName = profile.accountName ?: profile.studentNameLong ?: "" messageRecipientList.add(messageRecipientObject) } @@ -90,17 +125,16 @@ class LibrusMessagesGetMessage( teacher?.loginId = receiverLoginId val readDateText = message.select("readed").text() - val readDate = when (readDateText.isNotEmpty()) { + val readDate = when (readDateText.isNotNullNorEmpty()) { true -> Date.fromIso(readDateText) else -> 0 } val messageRecipientObject = MessageRecipientFull( - profileId, - receiverId, - -1, - readDate, - messageObject.id + profileId = profileId, + id = receiverId, + messageId = messageObject.id, + readDate = readDate ) messageRecipientObject.fullName = "$receiverFirstName $receiverLastName" @@ -111,19 +145,20 @@ class LibrusMessagesGetMessage( } if (!messageObject.seen) { - data.messageMetadataList.add(Metadata( + data.setSeenMetadataList.add(Metadata( messageObject.profileId, Metadata.TYPE_MESSAGE, messageObject.id, true, - true, - messageObject.addedDate + true )) } messageObject.recipients = messageRecipientList data.messageRecipientList.addAll(messageRecipientList) + data.messageList.add(messageObject) + data.messageListReplace = true EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt new file mode 100644 index 00000000..053258d8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-31. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import androidx.core.util.set +import androidx.room.OnConflictStrategy +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class LibrusMessagesGetRecipientList(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + private const val TAG = "LibrusMessagesGetRecipientList" + } + + private val listTypes = mutableListOf>() + + init { + messagesGet(TAG, "Receivers/action/GetTypes", parameters = mapOf( + "includeClass" to 1 + )) { doc -> + doc.select("response GetTypes data list ArrayItem")?.forEach { + val id = it.getElementsByTag("id")?.firstOrNull()?.ownText() ?: return@forEach + val name = it.getElementsByTag("name")?.firstOrNull()?.ownText() ?: return@forEach + listTypes += id to name + } + + getLists() + } + } + + private fun getLists() { + if (listTypes.isEmpty()) { + finish() + return + } + val type = listTypes.removeAt(0) + if (type.first == "contactsGroups") { + getLists() + return + } + messagesGetJson(TAG, "Receivers/action/GetListForType", parameters = mapOf( + "receiverType" to type.first + )) { json -> + val dataEl = json?.getJsonObject("response")?.getJsonObject("GetListForType")?.get("data") + if (dataEl is JsonObject) { + val listEl = dataEl.get("ArrayItem") + if (listEl is JsonArray) { + listEl.asJsonObjectList()?.forEach { item -> + processElement(item, type.first, type.second) + } + } + if (listEl is JsonObject) { + processElement(listEl, type.first, type.second) + } + } + + getLists() + } + } + + private fun processElement(element: JsonObject, typeId: String, typeName: String, listName: String? = null) { + val listEl = element.getJsonObject("list")?.get("ArrayItem") + if (listEl is JsonArray) { + listEl.asJsonObjectList()?.let { list -> + val label = element.getString("label") ?: "" + list.forEach { item -> + processElement(item, typeId, typeName, label) + } + return + } + } + if (listEl is JsonObject) { + val label = element.getString("label") ?: "" + processElement(listEl, typeId, typeName, label) + return + } + processRecipient(element, typeId, typeName, listName) + } + + private fun processRecipient(recipient: JsonObject, typeId: String, typeName: String, listName: String? = null) { + val id = recipient.getLong("id") ?: return + val label = recipient.getString("label") ?: return + + val fullNameLastFirst: String + val description: String? + if (typeId == "parentsCouncil" || typeId == "schoolParentsCouncil") { + val delimiterIndex = label.lastIndexOf(" - ") + if (delimiterIndex == -1) { + fullNameLastFirst = label.fixName() + description = null + } + else { + fullNameLastFirst = label.substring(0, delimiterIndex).fixName() + description = label.substring(delimiterIndex+3) + } + } + else { + fullNameLastFirst = label.fixName() + description = null + } + + var typeDescription: String? = null + val type = when (typeId) { + "tutors" -> Teacher.TYPE_EDUCATOR + "teachers" -> Teacher.TYPE_TEACHER + "classParents" -> Teacher.TYPE_PARENT + "guardians" -> Teacher.TYPE_PARENT + "parentsCouncil" -> { + typeDescription = joinNotNullStrings(": ", listName, description) + Teacher.TYPE_PARENTS_COUNCIL + } + "schoolParentsCouncil" -> { + typeDescription = joinNotNullStrings(": ", listName, description) + Teacher.TYPE_SCHOOL_PARENTS_COUNCIL + } + "pedagogue" -> Teacher.TYPE_PEDAGOGUE + "librarian" -> Teacher.TYPE_LIBRARIAN + "admin" -> Teacher.TYPE_SCHOOL_ADMIN + "secretary" -> Teacher.TYPE_SECRETARIAT + "sadmin" -> Teacher.TYPE_SUPER_ADMIN + else -> { + typeDescription = typeName + Teacher.TYPE_OTHER + } + } + + // get teacher by fullName AND type or create it + val teacher = data.teacherList.singleOrNull { + it.fullNameLastFirst == fullNameLastFirst && ((type != Teacher.TYPE_SCHOOL_ADMIN && type != Teacher.TYPE_PEDAGOGUE) || it.isType(type)) + } ?: Teacher(data.profileId, id).apply { + if (typeId == "sadmin" && id == 2L) { + name = "Pomoc" + surname = "Techniczna LIBRUS" + } + else { + name = fullNameLastFirst + fullNameLastFirst.splitName()?.let { + name = it.second + surname = it.first + } + } + data.teacherList[id] = this + } + + teacher.apply { + this.loginId = id.toString() + this.setTeacherType(type) + if (this.typeDescription.isNullOrBlank()) + this.typeDescription = typeDescription + } + } + + private fun finish() { + val event = RecipientListGetEvent( + data.profileId, + data.teacherList.filter { it.loginId != null } + ) + + profile?.lastReceiversSync = System.currentTimeMillis() + + data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE + EventBus.getDefault().postSticky(event) + onSuccess() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt new file mode 100644 index 00000000..d568135e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-2. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.base64Encode +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString + +class LibrusMessagesSendMessage(override val data: DataLibrus, + val recipients: List, + val subject: String, + val text: String, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + const val TAG = "LibrusMessages" + } + + init { + val params = mapOf( + "topic" to subject.base64Encode(), + "message" to text.base64Encode(), + "receivers" to recipients + .filter { it.loginId != null } + .joinToString(",") { it.loginId ?: "" }, + "actions" to "".base64Encode() + ) + + messagesGetJson(TAG, "SendMessage", parameters = params) { json -> + + val response = json.getJsonObject("response").getJsonObject("SendMessage") + val id = response.getLong("data") + + if (response.getString("status") != "ok" || id == null) { + val message = response.getString("message") + // TODO error + return@messagesGetJson + } + + LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = null) { + val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.id == id } + val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } + val event = MessageSentEvent(data.profileId, message, message?.addedDate) + + EventBus.getDefault().postSticky(event) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt new file mode 100644 index 00000000..6115e136 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages + +class LibrusMessagesTemplate(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + const val TAG = "LibrusMessages" + } + + init { + /* messagesGet(TAG, "") { doc -> + + data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_, SYNC_ALWAYS) + onSuccess() + } */ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusSandboxDownloadAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusSandboxDownloadAttachment.kt new file mode 100644 index 00000000..567071aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusSandboxDownloadAttachment.kt @@ -0,0 +1,117 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File +import kotlin.coroutines.CoroutineContext + +class LibrusSandboxDownloadAttachment(override val data: DataLibrus, + downloadLink: String, + val owner: Any, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusMessages(data, null), CoroutineScope { + companion object { + const val TAG = "LibrusSandboxDownloadAttachment" + } + + private var job = Job() + + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + private var getAttachmentCheckKeyTries = 0 + + init { + val keyMatcher = Regexes.LIBRUS_ATTACHMENT_KEY.find(downloadLink) + + when { + downloadLink.contains("CSDownloadFailed") -> { + data.error(ApiError(TAG, ERROR_LIBRUS_MESSAGES_ATTACHMENT_NOT_FOUND)) + onSuccess() + } + keyMatcher != null -> { + getAttachmentCheckKeyTries = 0 + + val attachmentKey = keyMatcher[1] + getAttachmentCheckKey(attachmentKey) { + downloadAttachment("${LIBRUS_SANDBOX_URL}CSDownload&singleUseKey=$attachmentKey", method = POST) + } + } + else -> { + downloadAttachment("$downloadLink/get", method = GET) + } + } + } + + private fun getAttachmentCheckKey(attachmentKey: String, callback: () -> Unit) { + sandboxGet(TAG, "CSCheckKey", + parameters = mapOf("singleUseKey" to attachmentKey)) { json -> + + when (json.getString("status")) { + "not_downloaded_yet" -> { + if (getAttachmentCheckKeyTries++ > 5) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withApiResponse(json)) + return@sandboxGet + } + launch { + delay(2000) + getAttachmentCheckKey(attachmentKey, callback) + } + } + + "ready" -> { + launch { callback() } + } + + else -> { + data.error(ApiError(TAG, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withApiResponse(json)) + } + } + } + } + + private fun downloadAttachment(url: String, method: Int = GET) { + val targetFile = File(Utils.getStorageDir(), attachmentName) + + sandboxGetFile(TAG, url, targetFile, { file -> + + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.ownerId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + EventBus.getDefault().postSticky(event) + + onSuccess() + + }) { written, _ -> + val event = AttachmentGetEvent( + profileId, + owner, + attachmentId, + AttachmentGetEvent.TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().postSticky(event) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt new file mode 100644 index 00000000..6b1f2eba --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt @@ -0,0 +1,24 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusSandboxDownloadAttachment +import pl.szczodrzynski.edziennik.data.db.entity.Message + +class LibrusSynergiaGetAttachment(override val data: DataLibrus, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetAttachment" + } + + init { + redirectUrlGet(TAG, "$LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL/${message.id}/$attachmentId") { url -> + LibrusSandboxDownloadAttachment(data, url, message, attachmentId, attachmentName, onSuccess) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetHomework.kt new file mode 100644 index 00000000..379bd42d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetHomework.kt @@ -0,0 +1,48 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import android.text.Html +import org.greenrobot.eventbus.EventBus +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent +import pl.szczodrzynski.edziennik.data.db.full.EventFull + +class LibrusSynergiaGetHomework(override val data: DataLibrus, + val event: EventFull, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetHomework" + } + + init { + synergiaGet(TAG, "moje_zadania/podglad/${event.id}") { text -> + val doc = Jsoup.parse(text) + + val table = doc.select("table.decorated tbody > tr") + + event.topic = table[1].select("td")[1].text() + event.homeworkBody = Html.fromHtml(table[5].select("td")[1].html()).toString() + + event.attachmentIds = mutableListOf() + event.attachmentNames = mutableListOf() + + if (table.size > 6) { + table[6].select("a").forEach { a -> + val attachmentId = a.attr("href").split('/') + .last().toLongOrNull() ?: return@forEach + val filename = a.text() + event.attachmentIds?.add(attachmentId) + event.attachmentNames?.add(filename) + } + } + + data.eventList.add(event) + data.eventListReplace = true + + EventBus.getDefault().postSticky(EventGetEvent(event)) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt new file mode 100644 index 00000000..4cb1e246 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt @@ -0,0 +1,160 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.greenrobot.eventbus.EventBus +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.swapFirstLastName +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessage(override val data: DataLibrus, + private val messageObject: MessageFull, + val onSuccess: () -> Unit) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetMessage" + } + + init { + val endpoint = when (messageObject.type) { + Message.TYPE_SENT -> "wiadomosci/1/6/${messageObject.id}/f0" + else -> "wiadomosci/1/5/${messageObject.id}/f0" + } + + data.profile?.also { profile -> + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + val messageElement = doc.select(".container-message tr")[0].child(1) + val detailsElement = messageElement.child(1) + val readElement = messageElement.children().last() + + val body = messageElement.select(".container-message-content").html() + + messageObject.apply { + this.body = body + + clearAttachments() + if (messageElement.children().size >= 5) { + messageElement.child(3).select("tr").forEachIndexed { i, attachment -> + if (i == 0) return@forEachIndexed // Skip the header + val filename = attachment.child(0).text().trim() + val attachmentId = "wiadomosci\\\\/pobierz_zalacznik\\\\/[0-9]+?\\\\/([0-9]+)\"".toRegex() + .find(attachment.select("img").attr("onclick"))?.get(1) + ?: return@forEachIndexed + addAttachment(attachmentId.toLong(), filename, -1) + } + } + } + + val messageRecipientList = mutableListOf() + + when (messageObject.type) { + Message.TYPE_RECEIVED -> { + val senderFullName = detailsElement.child(0).select(".left").text() + val senderGroupName = "\\[(.+?)]".toRegex().find(senderFullName)?.get(1)?.trim() + + data.teacherList.singleOrNull { it.id == messageObject.senderId }?.apply { + setTeacherType(when (senderGroupName) { + /* https://api.librus.pl/2.0/Messages/Role */ + "Pomoc techniczna Librus", "SuperAdministrator" -> Teacher.TYPE_SUPER_ADMIN + "Administrator szkoły" -> Teacher.TYPE_SCHOOL_ADMIN + "Dyrektor Szkoły" -> Teacher.TYPE_PRINCIPAL + "Nauczyciel" -> Teacher.TYPE_TEACHER + "Rodzic", "Opiekun" -> Teacher.TYPE_PARENT + "Sekretariat" -> Teacher.TYPE_SECRETARIAT + "Uczeń" -> Teacher.TYPE_STUDENT + "Pedagog/Psycholog szkolny" -> Teacher.TYPE_PEDAGOGUE + "Pracownik biblioteki" -> Teacher.TYPE_LIBRARIAN + "Inny specjalista" -> Teacher.TYPE_SPECIALIST + "Jednostka Nadrzędna" -> { + typeDescription = "Jednostka Nadrzędna" + Teacher.TYPE_OTHER + } + "Jednostka Samorządu Terytorialnego" -> { + typeDescription = "Jednostka Samorządu Terytorialnego" + Teacher.TYPE_OTHER + } + else -> Teacher.TYPE_OTHER + }) + } + + val readDateText = readElement.select(".left").text() + val readDate = when (readDateText.isNotNullNorEmpty()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = -1, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = profile.accountName + ?: profile.studentNameLong + + messageRecipientList.add(messageRecipientObject) + } + + Message.TYPE_SENT -> { + + readElement.select("tr").forEachIndexed { i, receiver -> + if (i == 0) return@forEachIndexed // Skip the header + + val receiverFullName = receiver.child(0).text() + val receiverName = receiverFullName.split('(')[0].swapFirstLastName() + + val teacher = data.teacherList.singleOrNull { it.fullName == receiverName } + val receiverId = teacher?.id ?: -1 + + val readDate = when (val readDateText = receiver.child(1).text().trim()) { + "NIE" -> 0 + else -> Date.fromIso(readDateText) + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = receiverId, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = receiverName + + messageRecipientList.add(messageRecipientObject) + } + } + } + + if (!messageObject.seen) { + data.setSeenMetadataList.add(Metadata( + messageObject.profileId, + Metadata.TYPE_MESSAGE, + messageObject.id, + true, + true + )) + } + + messageObject.recipients = messageRecipientList + data.messageRecipientList.addAll(messageRecipientList) + + data.messageList.add(messageObject) + data.messageListReplace = true + + EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) + onSuccess() + } + } ?: onSuccess() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt new file mode 100644 index 00000000..ffc8133a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt @@ -0,0 +1,116 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessages(override val data: DataLibrus, + override val lastSync: Long?, + private val type: Int = Message.TYPE_RECEIVED, + archived: Boolean = false, + val onSuccess: (Int) -> Unit) : LibrusSynergia(data, lastSync) { + companion object { + const val TAG = "LibrusSynergiaGetMessages" + } + + init { + val endpoint = when (type) { + Message.TYPE_RECEIVED -> "wiadomosci/5" + Message.TYPE_SENT -> "wiadomosci/6" + else -> null + } + val endpointId = when (type) { + Message.TYPE_RECEIVED -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED + else -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT + } + + if (endpoint != null) { + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + fun getRecipientId(name: String): Long = data.teacherList.singleOrNull { + it.fullNameLastFirst == name + }?.id ?: { + val teacherObject = Teacher( + profileId, + -1 * Utils.crc16(name.swapFirstLastName().toByteArray()).toLong(), + name.splitName()?.second!!, + name.splitName()?.first!! + ) + data.teacherList.put(teacherObject.id, teacherObject) + teacherObject.id + }.invoke() + + doc.select(".decorated.stretch tbody > tr").forEach { messageElement -> + val url = messageElement.select("a").first().attr("href") + val id = Regexes.LIBRUS_MESSAGE_ID.find(url)?.get(1)?.toLong() ?: return@forEach + val subject = messageElement.child(3).text() + val sentDate = Date.fromIso(messageElement.child(4).text()) + val recipientName = messageElement.child(2).text().split('(')[0].fixName() + val recipientId = getRecipientId(recipientName) + val read = messageElement.child(2).attr("style").isNullOrBlank() + + val senderId = when (type) { + Message.TYPE_RECEIVED -> recipientId + else -> null + } + + val receiverId = when (type) { + Message.TYPE_RECEIVED -> -1 + else -> recipientId + } + + val notified = when (type) { + Message.TYPE_SENT -> true + else -> read || profile?.empty ?: false + } + + val messageObject = Message( + profileId = profileId, + id = id, + type = type, + subject = subject, + body = null, + senderId = senderId, + addedDate = sentDate + ) + + val messageRecipientObject = MessageRecipient( + profileId, + receiverId, + -1, + if (read) 1 else 0, + id + ) + + messageObject.hasAttachments = !messageElement.child(1).select("img").isEmpty() + + data.messageList.add(messageObject) + data.messageRecipientList.add(messageRecipientObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + id, + notified, + notified + )) + } + + when (type) { + Message.TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) + Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, MainActivity.DRAWER_ITEM_MESSAGES) + } + onSuccess(endpointId) + } + } else { + data.error(TAG, ERROR_NOT_IMPLEMENTED) + onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt similarity index 50% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaHomework.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt index 860eb958..d33f6911 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt @@ -2,34 +2,38 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-22. */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia import org.jsoup.Jsoup import pl.szczodrzynski.edziennik.HOUR import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK -import pl.szczodrzynski.edziennik.api.v2.POST -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.POST +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.get import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.utils.models.Date -class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) { +class LibrusSynergiaHomework(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusSynergia(data, lastSync) { companion object { const val TAG = "LibrusSynergiaHomework" } - init { + init { data.profile?.also { profile -> synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf( "dataOd" to - if (data.profile?.empty != false) - profile!!.getSemesterStart(1).stringY_m_d + if (profile.empty) + profile.getSemesterStart(1).stringY_m_d else Date.getToday().stringY_m_d, - "dataDo" to profile!!.getSemesterEnd(profile?.currentSemester ?: 2).stringY_m_d, + "dataDo" to Date.getToday().stepForward(0, 0, 7).stringY_m_d, "przedmiot" to -1 )) { text -> @@ -38,8 +42,6 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () -> doc.select("table.myHomeworkTable > tbody").firstOrNull()?.also { homeworkTable -> val homeworkElements = homeworkTable.children() - val graphElements = doc.select("table[border].center td[align=left] tbody").first().children() - homeworkElements.forEachIndexed { i, el -> val elements = el.children() @@ -50,38 +52,32 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () -> val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id ?: -1 val topic = elements[2].text().trim() - val addedDate = Date.fromY_m_d(elements[4].text().trim()).inMillis + val addedDate = Date.fromY_m_d(elements[4].text().trim()) val eventDate = Date.fromY_m_d(elements[6].text().trim()) val id = "/podglad/([0-9]+)'".toRegex().find( elements[9].select("input").attr("onclick") )?.get(1)?.toLong() ?: return@forEachIndexed - val startTime = data.lessonList.singleOrNull { - it.weekDay == eventDate.weekDay && it.subjectId == subjectId - }?.startTime - val moreInfo = graphElements[2 * i + 1].select("td[title]") - .attr("title").trim() - val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo) - ?.get(1)?.replace("".toRegex(), "\n")?.trim() + val lessons = data.db.timetableDao().getAllForDateNow(profileId, eventDate) + val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime - val notified = when (profile?.empty) { + val seen = when (profile.empty) { true -> true - false -> Date.getToday() < eventDate - else -> false + else -> eventDate < Date.getToday() } val eventObject = Event( - profileId, - id, - eventDate, - startTime, - "$topic\n$description", - -1, - Event.TYPE_HOMEWORK, - false, - teacherId, - subjectId, - data.teamClass?.id ?: -1 + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -89,16 +85,17 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () -> profileId, Metadata.TYPE_HOMEWORK, id, - notified, - notified, - addedDate + seen, + seen )) } } - // because this requires a synergia login (2 more requests) sync this every two hours or if explicit :D - data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 2 * HOUR, DRAWER_ITEM_HOMEWORK) - onSuccess() + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + // because this requires a synergia login (2 more requests!!!) sync this every few hours or if explicit :D + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 5 * HOUR, DRAWER_ITEM_HOMEWORK) + onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) } - } + } ?: onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomeworkGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomeworkGetAttachment.kt new file mode 100644 index 00000000..a6d4b94b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomeworkGetAttachment.kt @@ -0,0 +1,25 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusSandboxDownloadAttachment +import pl.szczodrzynski.edziennik.data.db.full.EventFull + +class LibrusSynergiaHomeworkGetAttachment( + override val data: DataLibrus, + val event: EventFull, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaHomeworkGetAttachment" + } + + init { + redirectUrlGet(TAG, "$LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL/$attachmentId") { url -> + LibrusSandboxDownloadAttachment(data, url, event, attachmentId, attachmentName, onSuccess) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt similarity index 53% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaInfo.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt index 7777cf6a..66e11785 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/synergia/LibrusSynergiaInfo.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt @@ -2,15 +2,18 @@ * Copyright (c) Kacper Ziubryniewicz 2019-10-23 */ -package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia import org.jsoup.Jsoup import pl.szczodrzynski.edziennik.MONTH -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_INFO -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_INFO +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia -class LibrusSynergiaInfo(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) { +class LibrusSynergiaInfo(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusSynergia(data, lastSync) { companion object { const val TAG = "LibrusSynergiaInfo" } @@ -28,7 +31,7 @@ class LibrusSynergiaInfo(override val data: DataLibrus, val onSuccess: () -> Uni } data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_INFO, MONTH) - onSuccess() + onSuccess(ENDPOINT_LIBRUS_SYNERGIA_INFO) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt new file mode 100644 index 00000000..04a7f5b6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-26 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.db.entity.Metadata + +class LibrusSynergiaMarkAllAnnouncementsAsRead(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaMarkAllAnnouncementsAsRead" + } + + init { + synergiaGet(TAG, "ogloszenia") { + data.app.db.metadataDao().setAllSeen(profileId, Metadata.TYPE_ANNOUNCEMENT, true) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt new file mode 100644 index 00000000..ddfb4457 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia + +class LibrusSynergiaTemplate(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergia" + } + + init { + /* synergiaGet(TAG, "") { text -> + val doc = Jsoup.parse(text) + + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_, SYNC_ALWAYS) + onSuccess() + } */ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt new file mode 100644 index 00000000..68f44269 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt @@ -0,0 +1,133 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusPortal +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Profile + +class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LibrusFirstLogin" + } + + private val portal = LibrusPortal(data) + private val api = LibrusApi(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_LIBRUS + var firstProfileId = loginStoreId + + if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) { + // email login: use Portal for account list + LibrusLoginPortal(data) { + portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response -> + val accounts = json.getJsonArray("accounts") + + if (accounts == null || accounts.size() < 1) { + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore)) + onSuccess() + return@portalGet + } + val accountDataTime = json.getLong("lastModification") + + for (accountEl in accounts) { + val account = accountEl.asJsonObject + + val state = account.getString("state") + when (state) { + "requiring_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED + "need-activation" -> ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED + else -> null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return@portalGet + } + + val isParent = account.getString("group") == "parent" + + val id = account.getInt("id") ?: continue + val login = account.getString("login") ?: continue + val token = account.getString("accessToken") ?: continue + val tokenTime = (accountDataTime ?: 0) + DAY + val studentNameLong = account.getString("studentName").fixName() + val studentNameShort = studentNameLong.getShortName() + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.portalEmail, + studentNameLong, + studentNameShort, + if (isParent) studentNameLong else null /* temporarily - there is no parent name provided, only the type */ + ).apply { + studentData["accountId"] = id + studentData["accountLogin"] = login + studentData["accountToken"] = token + studentData["accountTokenTime"] = tokenTime + } + profileList.add(profile) + } + + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } + else { + // synergia or JST login: use Api for account info + LibrusLoginApi(data) { + api.apiGet(TAG, "Me") { json -> + + val me = json.getJsonObject("Me") + val account = me?.getJsonObject("Account") + val user = me?.getJsonObject("User") + + val login = account.getString("Login") + val isParent = account?.getInt("GroupId") in 5..6 + + val studentNameLong = buildFullName(user?.getString("FirstName"), user?.getString("LastName")) + val studentNameShort = studentNameLong.getShortName() + val accountNameLong = if (isParent) + buildFullName(account?.getString("FirstName"), account?.getString("LastName")) + else null + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + login, + studentNameLong, + studentNameShort, + accountNameLong + ).apply { + studentData["isPremium"] = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true + studentData["accountId"] = account.getInt("Id") ?: 0 + studentData["accountLogin"] = data.apiLogin ?: login + studentData["accountPassword"] = data.apiPassword + studentData["accountToken"] = data.apiAccessToken + studentData["accountTokenTime"] = data.apiTokenExpiryTime + studentData["accountRefreshToken"] = data.apiRefreshToken + } + profileList.add(profile) + + EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt similarity index 83% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLogin.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt index cfcd6fd6..c79acd2e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt @@ -2,14 +2,14 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-5. */ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_API -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_MESSAGES -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_PORTAL -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_SYNERGIA -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.utils.Utils class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { @@ -66,4 +66,4 @@ class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt similarity index 96% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt index 8b4e0542..7151cf29 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt @@ -2,22 +2,23 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-20. */ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.callback.JsonCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getInt import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* +@Suppress("ConvertSecondaryConstructorToPrimary") class LibrusLoginApi { companion object { private const val TAG = "LoginLibrusApi" @@ -137,6 +138,7 @@ class LibrusLoginApi { "librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR "librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED "invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN + "invalid_request" -> ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST else -> ERROR_LOGIN_LIBRUS_API_OTHER }.let { errorCode -> data.error(ApiError(TAG, errorCode) @@ -248,4 +250,4 @@ class LibrusLoginApi { .build() .enqueue() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt similarity index 76% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginMessages.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt index 99381fcb..9bfad99f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt @@ -2,16 +2,16 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-20. */ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.callback.TextCallbackHandler -import okhttp3.Cookie -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.LibrusRecaptchaHelper +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.io.StringWriter @@ -36,17 +36,39 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() } + text?.contains("grecaptcha.ready") == true -> { + val url = response?.request()?.url()?.toString() ?: run { + //data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + data.messagesLoginSuccessful = false + onSuccess() + return + } + + LibrusRecaptchaHelper(data.app, url, text, onSuccess = { newUrl -> + loginWithSynergia(newUrl) + }, onTimeout = { + //data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT, response, text) + data.messagesLoginSuccessful = false + onSuccess() + }) + } + text?.contains("ok") == true -> { saveSessionId(response, text) onSuccess() } text?.contains("Niepoprawny login i/lub hasło.") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) - text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + text?.contains("eAccessDeny") == true -> { + // data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + data.messagesLoginSuccessful = false + onSuccess() + } text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) text?.contains("error") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text?.contains("eVarWhitThisNameNotExists") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text?.contains("") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + else -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) } } @@ -64,21 +86,15 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { } if (data.isMessagesLoginValid()) { - data.app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("DZIENNIKSID") - .value(data.messagesSessionId!!) - .domain("wiadomosci.librus.pl") - .secure().httpOnly().build() - )) + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) onSuccess() } else { - data.app.cookieJar.clearForDomain("wiadomosci.librus.pl") + data.app.cookieJar.clear("wiadomosci.librus.pl") if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { loginWithSynergia() } - else if (data.apiLogin != null && data.apiPassword != null) { + else if (data.apiLogin != null && data.apiPassword != null && false) { loginWithCredentials() } else { @@ -101,7 +117,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { val loginElement = doc.createElement("login") loginElement.appendChild(doc.createTextNode(data.apiLogin)) dataElement.appendChild(loginElement) - val passwordElement = doc.createElement("login") + val passwordElement = doc.createElement("password") passwordElement.appendChild(doc.createTextNode(data.apiPassword)) dataElement.appendChild(passwordElement) val keyStrokeElement = doc.createElement("KeyStroke") @@ -148,8 +164,9 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { } private fun saveSessionId(response: Response?, text: String?) { - var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID") + var sessionId = data.app.cookieJar.get("wiadomosci.librus.pl", "DZIENNIKSID") sessionId = sessionId?.replace("-MAINT", "") // dunno what's this + sessionId = sessionId?.replace("MAINT", "") // dunno what's this if (sessionId == null) { data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID) .withResponse(response) @@ -159,4 +176,4 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { data.messagesSessionId = sessionId data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */ } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginPortal.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt index 19ffef22..f164e3ed 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginPortal.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt @@ -1,4 +1,4 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import android.util.Pair import com.google.gson.JsonObject @@ -7,14 +7,12 @@ import im.wangchao.mhttp.Response import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.getInt -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.utils.Utils.d -import java.net.HttpURLConnection.HTTP_UNAUTHORIZED +import java.net.HttpURLConnection.* import java.util.* import java.util.regex.Pattern @@ -38,11 +36,21 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() } else if (data.portalRefreshToken != null) { - data.app.cookieJar.clearForDomain("portal.librus.pl") + if (data.fakeLogin) { + data.app.cookieJar.clear("librus.szkolny.eu") + } + else { + data.app.cookieJar.clear("portal.librus.pl") + } accessToken(null, data.portalRefreshToken) } else { - data.app.cookieJar.clearForDomain("portal.librus.pl") + if (data.fakeLogin) { + data.app.cookieJar.clear("librus.szkolny.eu") + } + else { + data.app.cookieJar.clear("portal.librus.pl") + } authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL) } }} @@ -55,23 +63,31 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { .userAgent(LIBRUS_USER_AGENT) .withClient(data.app.httpLazy) .callback(object : TextCallbackHandler() { - override fun onSuccess(json: String, response: Response) { + override fun onSuccess(text: String, response: Response) { val location = response.headers().get("Location") if (location != null) { - val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) - if (authMatcher.find()) { - accessToken(authMatcher.group(1), null) - } else { - authorize(location) + val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) + when { + authMatcher.find() -> { + accessToken(authMatcher.group(1), null) + } + location.contains("rejected_client") -> { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID) + .withResponse(response) + .withApiResponse("Location: $location\n$text")) + } + else -> { + authorize(location) + } } } else { - val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(json) + val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text) if (csrfMatcher.find()) { login(csrfMatcher.group(1)) } else { data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING) .withResponse(response) - .withApiResponse(json)) + .withApiResponse(text)) } } } @@ -89,16 +105,35 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { private fun login(csrfToken: String) { d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}") + val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null) + val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L) + data.loginStore.removeLoginData("recaptchaCode") + data.loginStore.removeLoginData("recaptchaTime") + Request.builder() .url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL) .userAgent(LIBRUS_USER_AGENT) .addParameter("email", data.portalEmail) .addParameter("password", data.portalPassword) + .also { + if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */) + it.addParameter("g-recaptcha-response", recaptchaCode) + } .addHeader("X-CSRF-TOKEN", csrfToken) + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_FORBIDDEN) .contentType(MediaTypeUtils.APPLICATION_JSON) .post() .callback(object : JsonCallbackHandler() { override fun onSuccess(json: JsonObject?, response: Response) { + val location = response.headers()?.get("Location") + if (location == "$LIBRUS_REDIRECT_URL?command=close") { + data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE) + .withApiResponse(json) + .withResponse(response)) + return + } + if (json == null) { if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) @@ -109,8 +144,26 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { .withResponse(response)) return } - if (json.get("errors") != null) { - data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR) + val error = if (response.code() == 200) null else + json.getJsonArray("errors")?.getString(0) + ?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString + error?.let { code -> + when { + code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED + code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN + // this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set + code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL + code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN + else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + if (json.getBoolean("captchaRequired") == true) { + data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL) .withResponse(response) .withApiResponse(json)) return @@ -119,12 +172,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { } override fun onFailure(response: Response, throwable: Throwable) { - if (response.code() == 403 || response.code() == 401) { - data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN) - .withResponse(response) - .withThrowable(throwable)) - return - } data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) .withResponse(response) .withThrowable(throwable)) @@ -134,7 +181,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { .enqueue() } - private var refreshTokenFailed = false private fun accessToken(code: String?, refreshToken: String?) { d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL}") @@ -152,7 +198,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { return } val error = if (response?.code() == 200) null else - json.getString("hint") + json.getString("hint") ?: json.getString("error") error?.let { code -> when (code) { "Authorization code has expired" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED @@ -163,11 +209,9 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { "Check the `code` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE "Check the `refresh_token` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH "Check the `redirect_uri` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT - else -> when (json.getString("error")) { - "unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT - "invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID - else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER - } + "unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT + "invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID + else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER }.let { errorCode -> data.error(ApiError(TAG, errorCode) .withApiResponse(json) @@ -214,4 +258,4 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { .build() .enqueue() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt similarity index 80% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginSynergia.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt index 73f4ec9c..4a292cfe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginSynergia.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt @@ -2,23 +2,22 @@ * Copyright (c) Kuba Szczodrzyński 2019-9-20. */ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.TextCallbackHandler -import okhttp3.Cookie -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection -class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusApi(data) { +class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusApi(data, null) { companion object { private const val TAG = "LoginLibrusSynergia" } @@ -30,17 +29,11 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un } if (data.isSynergiaLoginValid()) { - data.app.cookieJar.saveFromResponse(null, listOf( - Cookie.Builder() - .name("DZIENNIKSID") - .value(data.synergiaSessionId!!) - .domain("synergia.librus.pl") - .secure().httpOnly().build() - )) + data.app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", data.synergiaSessionId) onSuccess() } else { - data.app.cookieJar.clearForDomain("synergia.librus.pl") + data.app.cookieJar.clear("synergia.librus.pl") if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) { loginWithApi() } @@ -70,7 +63,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un loginWithToken(json.getString("Token")) } - apiGet(TAG, "AutoLoginToken", POST, null, onSuccess) + apiGet(TAG, "AutoLoginToken", POST, onSuccess = onSuccess) } private fun loginWithToken(token: String?) { @@ -92,7 +85,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un } if (location?.endsWith("centrum_powiadomien") == true) { - val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID") + val sessionId = data.app.cookieJar.get("synergia.librus.pl", "DZIENNIKSID") if (sessionId == null) { data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID) .withResponse(response) @@ -117,7 +110,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un } } - data.app.cookieJar.clearForDomain("synergia.librus.pl") + data.app.cookieJar.clear("synergia.librus.pl") Request.builder() .url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien") .userAgent(LIBRUS_USER_AGENT) @@ -130,4 +123,4 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un .build() .enqueue() } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt similarity index 79% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt index 0d80cd65..5072e5a2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt @@ -1,12 +1,12 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login import com.google.gson.JsonObject import im.wangchao.mhttp.Response import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusPortal -import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusPortal +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.utils.Utils.d class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusPortal(data) { @@ -58,11 +58,6 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> data.apiAccessToken = accountToken data.apiTokenExpiryTime = response.getUnixDate() + 6 * 60 * 60 - // TODO remove this - data.profile?.studentNameLong = json.getString("studentName") - val nameParts = json.getString("studentName")?.split(" ") - data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0) - onSuccess() } } @@ -70,4 +65,4 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> portalGet(TAG, (if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL)+accountLogin, onSuccess = onSuccess) return true } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/DataMobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/DataMobidziennik.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt index d012186f..ddfb0113 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/DataMobidziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt @@ -2,18 +2,15 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-6. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik import android.util.LongSparseArray -import android.util.SparseArray -import android.util.SparseIntArray -import androidx.core.util.isNotEmpty import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_MOBIDZIENNIK_WEB -import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.currentTimeUnix -import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore -import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_MOBIDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -25,6 +22,10 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da && webSessionKey.isNotNullNorEmpty() && webServerId.isNotNullNorEmpty() + fun isApi2LoginValid() = loginEmail.isNotNullNorEmpty() + && loginId.isNotNullNorEmpty() + && globalId.isNotNullNorEmpty() + override fun satisfyLoginMethods() { loginMethods.clear() if (isWebLoginValid()) { @@ -32,23 +33,20 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da } } + override fun generateUserCode() = "$loginServerName:$loginUsername:$studentId" + val teachersMap = LongSparseArray() val subjectsMap = LongSparseArray() - val gradeAddedDates = LongSparseArray() - val gradeAverages = LongSparseArray() - val gradeColors = LongSparseArray() + val gradeAddedDates = sortedMapOf() + val gradeAverages = sortedMapOf() + val gradeColors = sortedMapOf() private var mLoginServerName: String? = null var loginServerName: String? get() { mLoginServerName = mLoginServerName ?: loginStore.getLoginData("serverName", null); return mLoginServerName } set(value) { loginStore.putLoginData("serverName", value); mLoginServerName = value } - private var mLoginEmail: String? = null - var loginEmail: String? - get() { mLoginEmail = mLoginEmail ?: loginStore.getLoginData("email", null); return mLoginEmail } - set(value) { loginStore.putLoginData("email", value); mLoginEmail = value } - private var mLoginUsername: String? = null var loginUsername: String? get() { mLoginUsername = mLoginUsername ?: loginStore.getLoginData("username", null); return mLoginUsername } @@ -90,6 +88,48 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("sessionIDTime", 0L); return mWebSessionIdExpiryTime ?: 0L } set(value) { loginStore.putLoginData("sessionIDTime", value); mWebSessionIdExpiryTime = value } + /* _____ _____ ___ + /\ | __ \_ _| |__ \ + / \ | |__) || | ) | + / /\ \ | ___/ | | / / + / ____ \| | _| |_ / /_ + /_/ \_\_| |_____| |___*/ + /** + * A global ID (whatever it is) used in API 2 + * and Firebase push from Mobidziennik. + */ + var globalId: String? + get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId } + set(value) { profile?.putStudentData("globalId", value) ?: return; mGlobalId = value } + private var mGlobalId: String? = null + + /** + * User's email that may or may not + * be retrieved from Web by [MobidziennikWebAccountEmail]. + * Used to log in to API 2. + */ + var loginEmail: String? + get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail } + set(value) { profile?.putStudentData("email", value); mLoginEmail = value } + private var mLoginEmail: String? = null + + /** + * A login ID used in the API 2. + * Looks more or less like "7063@2019@zslpoznan". + */ + var loginId: String? + get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId } + set(value) { profile?.putStudentData("loginId", value) ?: return; mLoginId = value } + private var mLoginId: String? = null + + /** + * No need to explain. + */ + var ciasteczkoAutoryzacji: String? + get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji } + set(value) { profile?.putStudentData("ciasteczkoAutoryzacji", value) ?: return; mCiasteczkoAutoryzacji = value } + private var mCiasteczkoAutoryzacji: String? = null + override fun saveData() { super.saveData() @@ -114,4 +154,4 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da var lessonNumber: Int, var signed: String ) -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt new file mode 100644 index 00000000..0e002576 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt @@ -0,0 +1,162 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin.MobidziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Mobidziennik" + + const val API_KEY = "szkolny_eu_72c7dbc8b97f1e5dd2d118cacf51c2b8543d15c0f65b7a59979adb0a1296b235d7febb826dd2a28688def6efe0811b924b04d7f3c7b7d005354e06dc56815d57" + } + + val internalErrorList = mutableListOf() + val data: DataMobidziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataMobidziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Mobidziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(mobidziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + MobidziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: MobidziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetMessage(data, message) { + completed() + } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebSendMessage(data, recipients, subject, text) { + completed() + } + } + } + + override fun markAllAnnouncementsAsRead() {} + override fun getAnnouncement(announcement: AnnouncementFull) {} + + override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetAttachment(data, owner, attachmentId, attachmentName) { + completed() + } + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetRecipientList(data) { + completed() + } + } + } + + override fun getEvent(eventFull: EventFull) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetHomework(data, eventFull) { + completed() + } + } + } + + override fun firstLogin() { MobidziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED, + ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY, + ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE, + ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID -> { + data.loginMethods.remove(LOGIN_METHOD_MOBIDZIENNIK_WEB) + data.prepareFor(mobidziennikLoginMethods, LOGIN_METHOD_MOBIDZIENNIK_WEB) + data.webSessionIdExpiryTime = 0 + login() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/MobidziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt similarity index 88% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/MobidziennikFeatures.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt index 234b41e7..7396ac52 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/MobidziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt @@ -2,10 +2,10 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-5. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.api.v2.models.Feature +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature const val ENDPOINT_MOBIDZIENNIK_API_MAIN = 1000 const val ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX = 2011 @@ -17,6 +17,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_NOTICES = 2040 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_API2_MAIN = 3000 val MobidziennikFeatures = listOf( @@ -27,11 +28,11 @@ val MobidziennikFeatures = listOf( ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN) // push config - /*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf( + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf( ENDPOINT_MOBIDZIENNIK_API2_MAIN to LOGIN_METHOD_MOBIDZIENNIK_API2 ), listOf(LOGIN_METHOD_MOBIDZIENNIK_API2)).withShouldSync { data -> - data.app.appConfig.fcmTokens[LOGIN_TYPE_MOBIDZIENNIK]?.second?.contains(data.profileId) == false - },*/ + !data.app.config.sync.tokenMobidziennikList.contains(data.profileId) + }, @@ -58,10 +59,12 @@ val MobidziennikFeatures = listOf( ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LOGIN_METHOD_MOBIDZIENNIK_WEB ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), - // attendance TODO implement website attendance scraping - /*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ATTENDANCE, listOf( + /** + * Attendance - only web scraping. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ATTENDANCE, listOf( ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB - ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)),*/ + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), @@ -104,4 +107,4 @@ val MobidziennikFeatures = listOf( ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 4 }*/ -) \ No newline at end of file +) 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 new file mode 100644 index 00000000..09e1e9fb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api.MobidziennikApi +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api2.MobidziennikApi2Main +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.* +import pl.szczodrzynski.edziennik.utils.Utils + +class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "MobidziennikData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_MOBIDZIENNIK_API_MAIN -> { + data.startProgress(R.string.edziennik_progress_endpoint_data) + MobidziennikApi(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_API2_MAIN -> { + data.startProgress(R.string.edziennik_progress_endpoint_push_config) + MobidziennikApi2Main(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + MobidziennikWebMessagesInbox(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + MobidziennikWebMessagesSent(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages) + MobidziennikWebMessagesAll(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR -> { + data.startProgress(R.string.edziennik_progress_endpoint_calendar) + MobidziennikWebCalendar(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + MobidziennikWebGrades(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL -> { + data.startProgress(R.string.edziennik_progress_endpoint_account_details) + MobidziennikWebAccountEmail(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + MobidziennikWebAttendance(data, lastSync, onSuccess) + }/* + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES -> { + data.startProgress(R.string.edziennik_progress_endpoint_behaviour) + MobidziennikWebNotices(data, lastSync, onSuccess) + }] + ENDPOINT_MOBIDZIENNIK_WEB_MANUALS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + MobidziennikWebManuals(data, lastSync, onSuccess) + }*/ + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt new file mode 100644 index 00000000..cfb4ab49 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File + +open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: Long?) { + companion object { + private const val TAG = "MobidziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + /* TODO + add error handling: + + +
    + +

    Ważna informacja!

    + +
    + Korzystasz z hasła, które zostało wygenerowane automatycznie.
    + Pamiętaj, aby je zmienić oraz uzupełnić dane w swoim profilu.

    + Obie te operacje można wykonać uruchamiając opcję + Moje konto->Edycja profilu. +
    + + */ + + fun webGet( + tag: String, + endpoint: String, + method: Int = GET, + parameters: List> = emptyList(), + fullUrl: String? = null, + onSuccess: (text: String) -> Unit + ) { + val url = fullUrl ?: "https://${data.loginServerName}.mobidziennik.pl$endpoint" + + d(tag, "Request: Mobidziennik/Web - $url") + + if (data.webSessionKey == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY) + return + } + if (data.webSessionValue == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE) + return + } + if (data.webServerId == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID) + return + } + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + if (text == "Nie jestes zalogowany" + || text.contains("przypomnij_haslo_email")) { + data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED) + .withResponse(response)) + return + } + + if (text.contains("

    Problemy z wydajnością

    ")) { + data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM) + .withResponse(response)) + return + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue) + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId) + + Request.builder() + .url(url) + .userAgent(MOBIDZIENNIK_USER_AGENT) + .apply { + when (method) { + GET -> get() + POST -> post() + } + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .callback(callback) + .build() + .enqueue() + } + + fun webGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit, + onProgress: (written: Long, total: Long) -> Unit) { + val url = "https://${data.loginServerName}.mobidziennik.pl$action" + + d(tag, "Request: Mobidziennik/Web - $url") + + if (data.webSessionKey == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY) + return + } + if (data.webSessionValue == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE) + return + } + if (data.webServerId == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID) + return + } + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue) + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId) + + Request.builder() + .url(url) + .userAgent(MOBIDZIENNIK_USER_AGENT) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApi.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt index 4a4069ed..323aeb14 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt @@ -2,17 +2,19 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_API_MAIN -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb -import pl.szczodrzynski.edziennik.api.v2.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.api.ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_API_MAIN +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS class MobidziennikApi(override val data: DataMobidziennik, - val onSuccess: () -> Unit) : MobidziennikWeb(data) { + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : MobidziennikWeb(data, lastSync) { companion object { private const val TAG = "MobidziennikApi" } @@ -37,7 +39,7 @@ class MobidziennikApi(override val data: DataMobidziennik, 9 -> MobidziennikApiTeams(data, null, rows) 14 -> MobidziennikApiGradeCategories(data, rows) 15 -> MobidziennikApiLessons(data, rows) - 16 -> MobidziennikApiAttendance(data, rows) + //16 -> MobidziennikApiAttendance(data, rows) // disabled since the new web scrapper is used 17 -> MobidziennikApiNotices(data, rows) 18 -> MobidziennikApiGrades(data, rows) 21 -> MobidziennikApiEvents(data, rows) @@ -47,7 +49,7 @@ class MobidziennikApi(override val data: DataMobidziennik, } data.setSyncNext(ENDPOINT_MOBIDZIENNIK_API_MAIN, SYNC_ALWAYS) - onSuccess() + onSuccess(ENDPOINT_MOBIDZIENNIK_API_MAIN) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt new file mode 100644 index 00000000..473b3691 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata + +class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { + init { run { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val studentId = cols[2].toInt() + if (studentId != data.studentId) + return@run + + val id = cols[0].toLong() + val lessonId = cols[1].toLong() + data.mobiLessons.singleOrNull { it.id == lessonId }?.let { lesson -> + val baseType = when (cols[4]) { + "2" -> TYPE_ABSENT + "5" -> TYPE_ABSENT_EXCUSED + "4" -> TYPE_RELEASED + else -> TYPE_PRESENT + } + val semester = data.profile?.dateToSemester(lesson.date) ?: 1 + + val typeName = when (baseType) { + TYPE_ABSENT -> "nieobecność" + TYPE_ABSENT_EXCUSED -> "nieobecność usprawiedliwiona" + TYPE_RELEASED -> "zwolnienie" + TYPE_PRESENT -> "obecność" + else -> "nieznany rodzaj" + } + val typeSymbol = when (baseType) { + TYPE_ABSENT -> "|" + TYPE_ABSENT_EXCUSED -> "+" + TYPE_RELEASED -> "z" + TYPE_PRESENT -> "." + else -> "?" + } + + val attendanceObject = Attendance( + profileId = data.profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = lesson.date, + startTime = lesson.startTime, + semester = semester, + teacherId = lesson.teacherId, + subjectId = lesson.subjectId + ).also { + it.lessonTopic = lesson.topic + } + + data.attendanceList.add(attendanceObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_ATTENDANCE, + id, + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN + )) + } + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiDates.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt similarity index 80% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiDates.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt index c74f79cd..a0b3dbc2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiDates.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt @@ -2,9 +2,9 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.utils.models.Date class MobidziennikApiDates(val data: DataMobidziennik, rows: List) { @@ -21,4 +21,4 @@ class MobidziennikApiDates(val data: DataMobidziennik, rows: List) { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt similarity index 62% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiEvents.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt index 446b5576..7a72d3de 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt @@ -2,13 +2,14 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api import androidx.core.util.contains -import pl.szczodrzynski.edziennik.api.v2.Regexes -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import java.text.ParseException @@ -29,7 +30,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { val teacherId = cols[1].toLong() val subjectId = cols[3].toLong() var type = Event.TYPE_DEFAULT - var topic = cols[5] + var topic = cols[5].trim() Regexes.MOBIDZIENNIK_EVENT_TYPE.find(topic)?.let { val typeText = it.groupValues[1] when (typeText) { @@ -50,17 +51,18 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { val eventObject = Event( - data.profileId, - id, - eventDate, - startTime, - topic, - -1, - type, - false, - teacherId, - subjectId, - teamId) + profileId = data.profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = type, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId, + addedDate = addedDate + ) data.eventList.add(eventObject) data.metadataList.add( @@ -69,10 +71,13 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { Metadata.TYPE_EVENT, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) } } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_DEFAULT)) + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_EXAM)) + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_SHORT_QUIZ)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt similarity index 82% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiGradeCategories.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt index d13c8ecd..b3bb8177 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiGradeCategories.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt @@ -2,12 +2,12 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-7. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api import android.graphics.Color import androidx.core.util.contains -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory class MobidziennikApiGradeCategories(val data: DataMobidziennik, rows: List) { init { @@ -38,4 +38,4 @@ class MobidziennikApiGradeCategories(val data: DataMobidziennik, rows: List) { - init { run { + init { data.profile?.also { profile -> run { data.db.gradeDao().getDetails( data.profileId, data.gradeAddedDates, @@ -60,18 +67,25 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { } val gradeObject = Grade( - data.profileId, - id, - category, - color, - description, - name, - value, - weight, - semester, - teacherId, - subjectId) - gradeObject.type = type + profileId = data.profileId, + id = id, + name = name, + type = type, + value = value, + weight = weight, + color = color, + category = category, + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId, + addedDate = addedDate + ) + + if (data.profile?.empty == true) { + addedDate = data.profile.dateSemester1Start.inMillis + } data.gradeList.add(gradeObject) data.metadataList.add( @@ -80,10 +94,20 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { Metadata.TYPE_GRADE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) addedDate++ } - }} -} \ No newline at end of file + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_YEAR_FINAL, + TYPE_YEAR_PROPOSED + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + }}} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt similarity index 53% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiHomework.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt index 4e074f26..683a8820 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt @@ -2,12 +2,14 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api +import android.text.Html import androidx.core.util.contains -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -24,22 +26,22 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { val id = cols[0].toLong() val teacherId = cols[7].toLong() val subjectId = cols[6].toLong() - val topic = cols[1] + val topic = Html.fromHtml(cols[1])?.toString()?.trim() ?: "" val eventDate = Date.fromYmd(cols[2]) val startTime = Time.fromYmdHm(cols[3]) val eventObject = Event( - data.profileId, - id, - eventDate, - startTime, - topic, - -1, - Event.TYPE_HOMEWORK, - false, - teacherId, - subjectId, - teamId) + profileId = data.profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId + ) data.eventList.add(eventObject) data.metadataList.add( @@ -48,10 +50,11 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { Metadata.TYPE_HOMEWORK, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiLessons.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt similarity index 89% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiLessons.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt index ee0579c4..47ab7494 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiLessons.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt @@ -2,9 +2,9 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-7. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -46,4 +46,4 @@ class MobidziennikApiLessons(val data: DataMobidziennik, rows: List) { data.mobiLessons.add(lesson) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt similarity index 64% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiNotices.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt index 5e9a4c23..66bfe1fd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt @@ -2,11 +2,11 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.utils.models.Date class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { @@ -33,12 +33,16 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { val addedDate = Date.fromYmd(cols[7]).inMillis val noticeObject = Notice( - data.profileId, - id, - text, - semester, - type, - teacherId) + profileId = data.profileId, + id = id, + type = type, + semester = semester, + text = text, + category = null, + points = null, + teacherId = teacherId, + addedDate = addedDate + ) data.noticeList.add(noticeObject) data.metadataList.add( @@ -47,9 +51,8 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { Metadata.TYPE_NOTICE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) } }} -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiStudent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt similarity index 87% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiStudent.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt index 01f7ecc4..2435478e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiStudent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt @@ -2,9 +2,9 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-6. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik class MobidziennikApiStudent(val data: DataMobidziennik, rows: List) { init { run { @@ -34,4 +34,4 @@ class MobidziennikApiStudent(val data: DataMobidziennik, rows: List) { e.printStackTrace(); }*/ }} -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiSubjects.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt similarity index 71% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiSubjects.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt index 541be25b..ce635860 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiSubjects.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt @@ -2,10 +2,10 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-6. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Subject class MobidziennikApiSubjects(val data: DataMobidziennik, rows: List) { init { @@ -22,4 +22,4 @@ class MobidziennikApiSubjects(val data: DataMobidziennik, rows: List) { data.subjectList.put(id, Subject(data.profileId, id, longName, shortName)) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTeams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt similarity index 83% rename from app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTeams.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt index 2d737899..6440b04e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTeams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt @@ -2,10 +2,10 @@ * Copyright (c) Kuba Szczodrzyński 2019-10-11. */ -package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api -import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Team import pl.szczodrzynski.edziennik.getById import pl.szczodrzynski.edziennik.values @@ -44,7 +44,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List? val studentId = cols[1].toInt() val teamId = cols[2].toLong() - val studentNumber = cols[4].toInt() + val studentNumber = cols[4].toIntOrNull() ?: -1 if (studentId != data.studentId) continue @@ -53,10 +53,11 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List? if (team.type == 1) { data.profile?.studentNumber = studentNumber data.teamClass = team + data.profile?.studentClassName = team.name } data.teamList.put(teamId, team) } } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt new file mode 100644 index 00000000..aa07b1c3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import android.util.SparseArray +import androidx.core.util.set +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.keys +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { + init { data.profile?.also { profile -> + val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") } + + val dataStart = Date.getToday() + val dataEnd = dataStart.clone().stepForward(0, 0, 7 + (6 - dataStart.weekDay)) + + data.toRemove.add(DataRemoveModel.Timetable.between(dataStart.clone(), dataEnd)) + + val dataDays = mutableListOf() + while (dataStart <= dataEnd) { + dataDays += dataStart.value + dataStart.stepForward(0, 0, 1) + } + + val lessonRanges = SparseArray
  • ", "
  • - ")) + } + + val scrollView = ScrollView(activity) + scrollView.addView(textView) + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.whats_new) + .setView(scrollView) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + }} +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt new file mode 100644 index 00000000..c6a7b2f9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-16. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.day + +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.android.synthetic.main.row_lesson_change_item.view.* +import kotlinx.android.synthetic.main.row_teacher_absence_item.view.* +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.databinding.DialogDayBinding +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog +import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.coroutines.CoroutineContext + +class DayDialog( + val activity: AppCompatActivity, + val profileId: Int, + val date: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "DayDialog" + } + + private lateinit var app: App + private lateinit var b: DialogDayBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var adapter: EventListAdapter + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogDayBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .setNeutralButton(R.string.add, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + EventManualDialog( + activity, + profileId, + defaultDate = date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + + update() + }} + + private fun update() { launch { + b.dayDate.setText( + R.string.dialog_day_date_format, + Week.getFullDayName(date.weekDay), + date.formattedString + ) + + val lessons = withContext(Dispatchers.Default) { + app.db.timetableDao().getAllForDateNow(profileId, date) + }.filter { it.type != Lesson.TYPE_NO_LESSONS } + + if (lessons.isNotEmpty()) { run { + val startTime = lessons.first().startTime ?: return@run + val endTime = lessons.last().endTime ?: return@run + val diff = Time.diff(startTime, endTime) + + b.lessonsInfo.setText( + R.string.dialog_day_lessons_info, + startTime.stringHM, + endTime.stringHM, + lessons.size.toString(), + diff.hour.toString(), + diff.minute.toString() + ) + + b.lessonsInfo.visibility = View.VISIBLE + }} + + val lessonChanges = withContext(Dispatchers.Default) { + app.db.timetableDao().getChangesForDateNow(profileId, date) + } + + lessonChanges.ifNotEmpty { + b.lessonChangeContainer.visibility = View.VISIBLE + b.lessonChangeContainer.lessonChangeCount.text = it.size.toString() + + b.lessonChangeLayout.onClick { + LessonChangeDialog( + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + } + + val teacherAbsences = withContext(Dispatchers.Default) { + app.db.teacherAbsenceDao().getAllByDateNow(profileId, date) + } + + teacherAbsences.ifNotEmpty { + b.teacherAbsenceContainer.visibility = View.VISIBLE + b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString() + + b.teacherAbsenceLayout.onClick { + TeacherAbsenceDialog( + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + } + + adapter = EventListAdapter( + activity, + showWeekDay = false, + showDate = false, + showType = true, + showTime = true, + showSubject = true, + markAsSeen = true, + onItemClick = { + EventDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + }, + onEventEditClick = { + EventManualDialog( + activity, + it.profileId, + editingEvent = it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ) + + app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events -> + adapter.items = events + if (b.eventsView.adapter == null) { + b.eventsView.adapter = adapter + b.eventsView.apply { + isNestedScrollingEnabled = false + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + adapter.notifyDataSetChanged() + + if (events != null && events.isNotEmpty()) { + b.eventsView.visibility = View.VISIBLE + b.eventsNoData.visibility = View.GONE + } else { + b.eventsView.visibility = View.GONE + b.eventsNoData.visibility = View.VISIBLE + } + }) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt deleted file mode 100644 index cc3a6d63..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-12. - */ - -package pl.szczodrzynski.edziennik.ui.dialogs.event - -import android.app.Activity -import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time - -class EventAddTypeDialog( - val activity: Activity, - val profileId: Int, - val date: Date? = null, - val time: Time? = null -) { - companion object { - private const val TAG = "EventAddTypeDialog" - } - - private lateinit var dialog: AlertDialog - - init { run { - dialog = MaterialAlertDialogBuilder(activity) - .setItems(R.array.main_menu_add_options) { dialog, which -> - dialog.dismiss() - EventManualDialog(activity, profileId) - .show( - activity.application as App, - null, - date, - time, - when (which) { - 1 -> EventManualDialog.DIALOG_HOMEWORK - else -> EventManualDialog.DIALOG_EVENT - } - ) - - } - .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } - .show() - }} -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt new file mode 100644 index 00000000..b1683ac5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -0,0 +1,359 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-18. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.provider.CalendarContract +import android.provider.CalendarContract.Events +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.utils.BetterLink +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class EventDetailsDialog( + val activity: AppCompatActivity, + val event: EventFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "EventDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: DialogEventDetailsBinding + private lateinit var dialog: AlertDialog + private var removeEventDialog: AlertDialog? = null + private val eventShared = event.sharedBy != null + private val eventOwn = event.sharedBy == "self" + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val api by lazy { + SzkolnyApi(app) + } + + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + EventBus.getDefault().register(this) + app = activity.applicationContext as App + b = DialogEventDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .apply { + if (event.addedManually) + setNeutralButton(R.string.remove, null) + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + EventBus.getDefault().unregister(this@EventDetailsDialog) + progressDialog?.dismiss() + } + .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + showRemoveEventDialog() + } + + update() + }} + + private fun update() { + b.event = event + b.eventShared = eventShared + b.eventOwn = eventOwn + + val bullet = " • " + val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) + + try { + b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.date.month - 1] + } + catch (_: Exception) {} + + b.typeColor.background?.setTintColor(event.eventColor) + + b.details = mutableListOf( + event.subjectLongName, + event.teamName?.asColoredSpannable(colorSecondary) + ).concat(bullet) + + b.addedBy.setText( + when (event.sharedBy) { + null -> when { + event.addedManually -> R.string.event_details_added_by_self_format + event.teacherName == null -> R.string.event_details_added_by_unknown_format + else -> R.string.event_details_added_by_format + } + "self" -> R.string.event_details_shared_by_self_format + else -> R.string.event_details_shared_by_format + }, + Date.fromMillis(event.addedDate).formattedString, + event.sharedByName ?: event.teacherName ?: "" + ) + + + // MARK AS DONE + b.checkDoneButton.isChecked = event.isDone + b.checkDoneButton.onChange { _, isChecked -> + if (isChecked && !event.isDone) { + b.checkDoneButton.isChecked = false + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.event_mark_as_done_title) + .setMessage(R.string.event_mark_as_done_text) + .setPositiveButton(R.string.ok) { _, _ -> + event.isDone = isChecked + launch(Dispatchers.Default) { + app.db.eventDao().replace(event) + } + b.checkDoneButton.isChecked = true + } + .setNegativeButton(R.string.cancel, null) + .show() + } + else if (!isChecked && event.isDone) { + event.isDone = isChecked + launch(Dispatchers.Default) { + app.db.eventDao().replace(event) + } + } + } + b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done) + + // EDIT EVENT + b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE + b.editButton.setOnClickListener { + EventManualDialog( + activity, + event.profileId, + editingEvent = event, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + b.editButton.attachToastHint(R.string.hint_edit_event) + + // SAVE IN CALENDAR + b.saveInCalendarButton.onClick { + openInCalendar() + } + b.saveInCalendarButton.attachToastHint(R.string.hint_save_in_calendar) + + // GO TO TIMETABLE + b.goToTimetableButton.onClick { + dialog.dismiss() + val dateStr = event.date.stringY_m_d + + val intent = + if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE) + Intent(TimetableFragment.ACTION_SCROLL_TO_DATE) + else if (activity is MainActivity) + Intent("android.intent.action.MAIN") + else + Intent(activity, MainActivity::class.java) + + intent.apply { + putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE) + putExtra("timetableDate", dateStr) + } + if (activity is MainActivity) + activity.sendBroadcast(intent) + else + activity.startActivity(intent) + } + b.goToTimetableButton.attachToastHint(R.string.hint_go_to_timetable) + + // RE-DOWNLOAD + b.downloadButton.isVisible = App.debugMode + b.downloadButton.onClick { + EdziennikTask.eventGet(event.profileId, event).enqueue(activity) + } + b.downloadButton.attachToastHint(R.string.hint_download_again) + + + b.topic.text = event.topic + BetterLink.attach(b.topic) { + dialog.dismiss() + } + + if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) { + b.bodyTitle.isVisible = true + b.bodyProgressBar.isVisible = true + b.body.isVisible = false + EdziennikTask.eventGet(event.profileId, event).enqueue(activity) + } + else if (event.homeworkBody.isNullOrBlank()) { + b.bodyTitle.isVisible = false + b.bodyProgressBar.isVisible = false + b.body.isVisible = false + } + else { + b.bodyTitle.isVisible = true + b.bodyProgressBar.isVisible = false + b.body.isVisible = true + b.body.text = event.homeworkBody + BetterLink.attach(b.body) { + dialog.dismiss() + } + } + + if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) { + b.attachmentsTitle.isVisible = false + b.attachmentsFragment.isVisible = false + } + else { + b.attachmentsTitle.isVisible = true + b.attachmentsFragment.isVisible = true + b.attachmentsFragment.init(Bundle().also { + it.putInt("profileId", event.profileId) + it.putLongArray("attachmentIds", event.attachmentIds!!.toLongArray()) + it.putStringArray("attachmentNames", event.attachmentNames!!.toTypedArray()) + }, owner = event) + } + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onEventGetEvent(event: EventGetEvent) { + EventBus.getDefault().removeStickyEvent(event) + if (event.event.homeworkBody == null) + event.event.homeworkBody = "" + update() + } + + private fun showRemovingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_removing_text) + .setCancelable(false) + .show() + } + + private fun showRemoveEventDialog() { + val shareNotice = when { + eventShared && eventOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self) + eventShared && !eventOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared) + else -> "" + } + removeEventDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(activity.getString(R.string.dialog_register_event_manual_remove_confirmation)+shareNotice) + .setPositiveButton(R.string.yes, null) + .setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() } + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + removeEvent() + } + } + + show() + } + } + + private fun removeEvent() { + launch { + if (eventShared && eventOwn) { + // unshare + remove own event + showRemovingProgressDialog() + + api.runCatching(activity) { + unshareEvent(event) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + finishRemoving() + } else if (eventShared && !eventOwn) { + // remove + blacklist somebody's event + Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show() + // TODO + } else { + // remove event + Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() + finishRemoving() + } + progressDialog?.dismiss() + } + } + + private fun finishRemoving() { + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().remove(event) + } + } + + removeEventDialog?.dismiss() + dialog.dismiss() + Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } + + private fun openInCalendar() { launch { + val title = event.typeName ?: "" + + (if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") + + (event.subjectLongName ?: "") + + val intent = Intent(Intent.ACTION_EDIT).apply { + data = Events.CONTENT_URI + putExtra(Events.TITLE, title) + putExtra(Events.DESCRIPTION, event.topic) + + if (event.time == null) { + putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true) + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.date.inMillis) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.date.inMillis) + } else { + val startTime = event.date.combineWith(event.time) + val endTime = startTime + 45 * 60 * 1000 /* 45 min */ + + putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false) + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startTime) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime) + } + } + + try { + activity.startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText(activity, R.string.calendar_app_not_found, Toast.LENGTH_SHORT).show() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.java deleted file mode 100644 index ccf28951..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.java +++ /dev/null @@ -1,133 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.dialogs.event; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.mikepenz.iconics.view.IconicsImageView; -import com.mikepenz.iconics.view.IconicsTextView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.modules.events.Event; -import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK; -import static pl.szczodrzynski.edziennik.utils.Utils.bs; -import static pl.szczodrzynski.edziennik.utils.Utils.d; - -public class EventListAdapter extends RecyclerView.Adapter { - private static final String TAG = "EventListAdapter"; - private Context context; - private List examList; - private EventListDialog parentDialog; - - //getting the context and product list with constructor - public EventListAdapter(Context mCtx, List examList, EventListDialog parentDialog) { - this.context = mCtx; - this.examList = examList; - this.parentDialog = parentDialog; - } - - @NonNull - @Override - public EventListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_dialog_event_list_item, parent, false); - return new EventListAdapter.ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull EventListAdapter.ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - EventFull event = examList.get(position); - - if (event.type == TYPE_HOMEWORK) { - holder.eventListItemRoot.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.CLEAR)); - } - else { - holder.eventListItemRoot.getBackground().setColorFilter(new PorterDuffColorFilter(event.getColor(), PorterDuff.Mode.MULTIPLY)); - } - - holder.eventListItemStartTime.setText(event.startTime == null ? app.getString(R.string.event_all_day) : event.startTime.getStringHM()); - //holder.examListItemEndTime.setText(event.endTime.getStringHM()); - holder.eventListItemTeamName.setText(Utils.bs(event.teamName)); - holder.eventListItemTeacherName.setText(bs(null, event.teacherFullName, "\n") + bs(event.subjectLongName)); - holder.eventListItemAddedDate.setText(Date.fromMillis(event.addedDate).getFormattedStringShort()); - holder.eventListItemType.setText(event.typeName); - holder.eventListItemTopic.setText(event.topic); - holder.eventListItemEdit.setVisibility((event.addedManually ? View.VISIBLE : View.GONE)); - holder.eventListItemHomework.setVisibility((event.type == Event.TYPE_HOMEWORK ? View.VISIBLE : View.GONE)); - holder.eventListItemEdit.setOnClickListener(v -> { - if (parentDialog != null) { - parentDialog.callDismissListener = false; - parentDialog.dialog.dismiss(); - } - d(TAG, "Event "+event); - if (event.type == Event.TYPE_HOMEWORK) { - new EventManualDialog(context, event.profileId) - .withDismissListener(parentDialog::performDismiss) - .show(app, event, null, null, EventManualDialog.DIALOG_HOMEWORK); - } - else { - new EventManualDialog(context, event.profileId) - .withDismissListener(parentDialog::performDismiss) - .show(app, event, null, null, EventManualDialog.DIALOG_EVENT); - } - }); - - if (event.sharedBy == null) { - holder.eventListItemSharedBy.setVisibility(View.GONE); - } - else if (event.sharedByName != null) { - holder.eventListItemSharedBy.setText(app.getString(R.string.event_shared_by_format, (event.sharedBy.equals("self") ? app.getString(R.string.event_shared_by_self) : event.sharedByName))); - } - } - - @Override - public int getItemCount() { - return examList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - ConstraintLayout eventListItemRoot; - TextView eventListItemStartTime; - TextView eventListItemTeacherName; - TextView eventListItemType; - TextView eventListItemTopic; - TextView eventListItemAddedDate; - TextView eventListItemTeamName; - IconicsImageView eventListItemEdit; - IconicsImageView eventListItemHomework; - IconicsTextView eventListItemSharedBy; - - - ViewHolder(View itemView) { - super(itemView); - eventListItemRoot = itemView.findViewById(R.id.eventListItemRoot); - eventListItemStartTime = itemView.findViewById(R.id.eventListItemStartTime); - eventListItemTeacherName = itemView.findViewById(R.id.eventListItemTeacherName); - eventListItemType = itemView.findViewById(R.id.eventListItemType); - eventListItemTopic = itemView.findViewById(R.id.eventListItemTopic); - eventListItemAddedDate = itemView.findViewById(R.id.eventListItemAddedDate); - eventListItemTeamName = itemView.findViewById(R.id.eventListItemTeamName); - eventListItemEdit = itemView.findViewById(R.id.eventListItemEdit); - eventListItemHomework = itemView.findViewById(R.id.eventListItemHomework); - eventListItemSharedBy = itemView.findViewById(R.id.eventListItemSharedBy); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt new file mode 100644 index 00000000..34cc6a2d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-30 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.databinding.EventListItemBinding +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.coroutines.CoroutineContext + +class EventListAdapter( + val context: Context, + val simpleMode: Boolean = false, + val showWeekDay: Boolean = false, + val showDate: Boolean = false, + val showType: Boolean = true, + val showTime: Boolean = true, + val showSubject: Boolean = true, + val markAsSeen: Boolean = true, + val onItemClick: ((event: EventFull) -> Unit)? = null, + val onEventEditClick: ((event: EventFull) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + + private val app = context.applicationContext as App + private val manager = app.eventManager + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = EventListItemBinding.inflate(inflater, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val event = items[position] + val b = holder.b + val manager = app.eventManager + + b.root.onClick { + onItemClick?.invoke(event) + if (!event.seen) { + manager.markAsSeen(event) + } + if (event.showAsUnseen == true) { + event.showAsUnseen = false + notifyItemChanged(event) + } + } + + val bullet = " • " + + b.simpleMode = simpleMode + + b.topic.text = event.topic + b.topic.maxLines = if (simpleMode) 2 else 3 + + b.details.text = mutableListOf( + if (showWeekDay) Week.getFullDayName(event.date.weekDay) else null, + if (showDate) event.date.getRelativeString(context, 7) ?: event.date.formattedStringShort else null, + if (showType) event.typeName else null, + if (showTime) event.time?.stringHM ?: app.getString(R.string.event_all_day) else null, + if (showSubject) event.subjectLongName else null + ).concat(bullet) + + b.addedBy.setText( + when (event.sharedBy) { + null -> when { + event.addedManually -> R.string.event_list_added_by_self_format + event.teacherName == null -> R.string.event_list_added_by_unknown_format + else -> R.string.event_list_added_by_format + } + "self" -> R.string.event_list_shared_by_self_format + else -> R.string.event_list_shared_by_format + }, + Date.fromMillis(event.addedDate).formattedString, + event.sharedByName ?: event.teacherName ?: "", + event.teamName?.let { bullet+it } ?: "" + ) + + b.typeColor.background?.setTintColor(event.eventColor) + b.typeColor.isVisible = showType + + b.editButton.isVisible = !simpleMode && event.addedManually && !event.isDone + b.editButton.onClick { + onEventEditClick?.invoke(event) + } + b.editButton.attachToastHint(R.string.hint_edit_event) + + b.isDone.isVisible = event.isDone + + if (event.showAsUnseen == null) + event.showAsUnseen = !event.seen + + b.unread.isVisible = event.showAsUnseen == true + if (markAsSeen && !event.seen) { + manager.markAsSeen(event) + } + } + + private fun notifyItemChanged(model: Any) { + startCoroutineTimer(1000L, 0L) { + val index = items.indexOf(model) + if (index != -1) + notifyItemChanged(index) + } + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: EventListItemBinding) : RecyclerView.ViewHolder(b.root) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListDialog.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListDialog.java deleted file mode 100644 index 065eedda..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListDialog.java +++ /dev/null @@ -1,230 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.dialogs.event; - -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Typeface; -import android.os.AsyncTask; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.afollestad.materialdialogs.MaterialDialog; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.cardview.widget.CardView; -import androidx.lifecycle.LifecycleOwner; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange; -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; -import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull; -import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog; -import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; - -public class EventListDialog { - private App app; - private Context context; - private int profileId; - - public EventListDialog(Context context) { - this.context = context; - this.profileId = App.profileId; - } - public EventListDialog(Context context, int profileId) { - this.context = context; - this.profileId = profileId; - } - - public MaterialDialog dialog; - private View dialogView; - private RecyclerView examsView; - private DialogInterface.OnDismissListener dismissListener; - public boolean callDismissListener = true; - private LessonFull lesson; - - public EventListDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) { - this.dismissListener = dismissListener; - return this; - } - - public void performDismiss(DialogInterface dialogInterface) { - if (callDismissListener && dismissListener != null) { - dismissListener.onDismiss(dialogInterface); - } - callDismissListener = true; - } - - public void show(App _app, Date date) - { - show(_app, date, null); - } - public void show(App _app, Date date, Time time) { - show(_app, date, time, false); - } - public void show(App _app, Date date, Time time, boolean noDefaultTimeWhenAdding) { - if (!(context instanceof AppCompatActivity) || date == null) - return; - this.app = _app; - - if (time != null) { - AsyncTask.execute(() -> { - this.lesson = app.db.lessonDao().getByDateTimeNow(profileId, date, time); - ((AppCompatActivity) context).runOnUiThread(() -> { - actualShow(date, time, noDefaultTimeWhenAdding); - }); - }); - return; - } - actualShow(date, null, noDefaultTimeWhenAdding); - } - - private void actualShow(Date date, Time time, boolean noDefaultTimeWhenAdding) { - dialog = new MaterialDialog.Builder(context) - .title((time == null ? date.getFormattedString() : (lesson != null ? lesson.getSubjectLongName() : date.getFormattedString())+", "+time.getStringHM())) - .customView(R.layout.dialog_event_list, false) - .neutralText(R.string.add) - .positiveText(R.string.close) - .autoDismiss(false) - .onNeutral((dialog, which) -> { - callDismissListener = false; - dialog.dismiss(); - //performDismiss(dialog); - //callDismissListener = true; - new MaterialDialog.Builder(context) - .title(R.string.main_menu_add) - .items(R.array.main_menu_add_options) - .autoDismiss(true) - .itemsCallback((dialog1, itemView, position, text) -> { - callDismissListener = false; - //performDismiss(dialog1); // use if the main dialog is already dismissed - //dialog1.dismiss(); - //callDismissListener = true; - switch (position) { - case 0: - new EventManualDialog(context, profileId) - .withDismissListener(this::performDismiss) - .show(app, null, date, (noDefaultTimeWhenAdding ? null : time), EventManualDialog.DIALOG_EVENT); - break; - case 1: - new EventManualDialog(context, profileId) - .withDismissListener(this::performDismiss) - .show(app, null, date, (noDefaultTimeWhenAdding ? null : time), EventManualDialog.DIALOG_HOMEWORK); - break; - } - }) - .dismissListener((dialog1 -> { - callDismissListener = false; - performDismiss(dialog1); - })) - .show(); - }) - .onPositive((dialog, which) -> dialog.dismiss()) - .dismissListener(this::performDismiss) - .show(); - - dialogView = dialog.getCustomView(); - assert dialogView != null; - - LinearLayout eventListLessonDetails = dialogView.findViewById(R.id.eventListLessonDetails); - eventListLessonDetails.setVisibility((lesson == null ? View.GONE : View.VISIBLE)); - if (lesson != null) { - TextView eventListClassroom = dialogView.findViewById(R.id.eventListClassroom); - TextView eventListTeacher = dialogView.findViewById(R.id.eventListTeacher); - TextView eventListLessonChange = dialogView.findViewById(R.id.eventListLessonChange); - ((TextView)dialogView.findViewById(R.id.eventListLessonDate)).setText(app.getString(R.string.date_time_format, date.getFormattedString(), "")); - - boolean lessonCancelled = false; - if (lesson.changeId != 0 && lesson.changeType == LessonChange.TYPE_CANCELLED) { - lessonCancelled = true; - } - - if (lessonCancelled) { - eventListLessonChange.setText(R.string.lesson_cancelled); - eventListLessonChange.setTypeface(null, Typeface.BOLD_ITALIC); - eventListTeacher.setVisibility(View.GONE); - eventListClassroom.setVisibility(View.GONE); - eventListLessonChange.setVisibility(View.VISIBLE); - } else { - eventListLessonChange.setText(lesson.getSubjectLongName(true)); - if (lesson.changedSubjectLongName()) { - eventListLessonChange.setTypeface(null, Typeface.ITALIC); - } else { - eventListLessonChange.setVisibility(View.GONE); - } - - eventListTeacher.setText(lesson.getTeacherFullName(true)); - eventListTeacher.setTypeface(null, lesson.changedTeacherFullName() ? Typeface.ITALIC : Typeface.NORMAL); - - eventListClassroom.setText(lesson.getClassroomName(true)); - eventListClassroom.setTypeface(null, lesson.changedClassroomName() ? Typeface.ITALIC : Typeface.NORMAL); - } - - } - - //((TextView)dialogView.findViewById(R.id.textLessonDate)).setText(date.getFormattedString()+(time != null ? ", "+time.getStringHM() : "")); - - examsView = dialogView.findViewById(R.id.eventListView); - examsView.setHasFixedSize(false); - examsView.setNestedScrollingEnabled(true); - examsView.setLayoutManager(new LinearLayoutManager(context)); - - CardView lessonChangeContainer = dialogView.findViewById(R.id.lessonChangeContainer); - CardView teacherAbsenceContainer = dialogView.findViewById(R.id.teacherAbsenceContainer); - //lessonChangeContainer.setVisibility(View.GONE); - if (time == null) { - app.db.lessonChangeDao().getLessonChangeCounterByDate(App.profileId, date).observe((LifecycleOwner) context, counter -> { - if (counter == null) - return; - if (counter.lessonChangeCount > 0) { - lessonChangeContainer.setVisibility(View.VISIBLE); - TextView lessonChangeCount = dialogView.findViewById(R.id.lessonChangeCount); - lessonChangeCount.setText(String.valueOf(counter.lessonChangeCount)); - lessonChangeContainer.setCardBackgroundColor(0xff78909c); - lessonChangeContainer.setOnClickListener((v -> { - new LessonChangeDialog(context).show(app, date); - })); - } - }); - - app.db.teacherAbsenceDao().getAllByDateFull(App.profileId, date).observe((LifecycleOwner) context, teacherAbsenceList -> { - if (teacherAbsenceList == null) - return; - if (teacherAbsenceList.size() > 0) { - int count = 0; - for (TeacherAbsenceFull teacherAbsence : teacherAbsenceList) { - Date dateFrom = teacherAbsence.getDateFrom(); - Date dateTo = teacherAbsence.getDateTo(); - - if (date.compareTo(dateFrom) >= 0 && date.compareTo(dateTo) <= 0) { - count++; - } - } - - teacherAbsenceContainer.setVisibility(View.VISIBLE); - TextView teacherAbsenceCount = dialogView.findViewById(R.id.teacherAbsenceCount); - teacherAbsenceCount.setText(String.valueOf(count)); - teacherAbsenceContainer.setCardBackgroundColor(0xffff1744); - teacherAbsenceContainer.setOnClickListener(( v -> { - new TeacherAbsenceDialog(context).show(app, date); - })); - } - }); - } - - app.db.eventDao().getAllByDateTime(profileId, date, time).observe((LifecycleOwner) context, events -> { - if (events == null || events.size() == 0) { - examsView.setVisibility(View.GONE); - dialogView.findViewById(R.id.textNoEvents).setVisibility(View.VISIBLE); - } - else { - EventListAdapter adapter = new EventListAdapter(context, events, this); - examsView.setAdapter(adapter); - } - }); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.java deleted file mode 100644 index f3e747e5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.java +++ /dev/null @@ -1,885 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.dialogs.event; - - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.ColorInt; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.PopupMenu; -import androidx.appcompat.widget.SwitchCompat; -import androidx.lifecycle.Observer; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; -import com.google.gson.JsonObject; -import com.jaredrummler.android.colorpicker.ColorPickerDialog; -import com.jaredrummler.android.colorpicker.ColorPickerDialogListener; -import com.mikepenz.iconics.IconicsColor; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.IconicsSize; -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; -import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.MainActivity; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.modules.events.Event; -import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; -import pl.szczodrzynski.edziennik.data.db.modules.events.EventType; -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata; -import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull; -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject; -import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher; -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.TextInputDropDown; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; -import pl.szczodrzynski.edziennik.utils.models.Week; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.COLOR_DEFAULT; -import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK; -import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED; -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.REGISTRATION_ENABLED; -import static pl.szczodrzynski.edziennik.utils.Utils.bs; -import static pl.szczodrzynski.edziennik.utils.Utils.ns; - -public class EventManualDialog { - private static final String TAG = "EventManualDialog"; - private App app; - private Context context; - private int profileId; - private ProfileFull profile = null; - - public EventManualDialog(Context context, int profileId) { - this.context = context; - this.activity = (AppCompatActivity) context; - this.profileId = profileId; - } - public EventManualDialog(Context context) { - this.context = context; - this.activity = (AppCompatActivity) context; - this.profileId = App.profileId; - } - - - private AppCompatActivity activity; - private MaterialDialog dialog; - private View dialogView; - private TextInputLayout registerEventManualDateLayout; - private TextInputDropDown registerEventManualDate; - private TextInputLayout registerEventManualLessonLayout; - private TextInputDropDown registerEventManualLesson; - private TextInputLayout registerEventManualTeamLayout; - private TextInputDropDown registerEventManualTeam; - private SwitchCompat registerEventManualShare; - private TextView registerEventManualShareText; - private TextInputLayout registerEventManualTypeLayout; - private TextInputDropDown registerEventManualType; - private TextInputLayout registerEventManualTopicLayout; - private TextInputEditText registerEventManualTopic; - private TextInputDropDown registerEventManualTeacher; - private TextInputDropDown registerEventManualSubject; - private View registerEventManualColorPreview; - private Button registerEventManualColorChoose; - private Date lessonDate = Date.getToday(); - private Time lessonStartTime = null; - private long lessonTeamId = -1; - private long lessonTeacherId = -1; - private long lessonSubjectId = -1; - private boolean lessonSelected = false; - private int eventType = TYPE_UNDEFINED; - private int eventColor = -1; - private EventFull editingEvent = null; - private MaterialDialog progressDialog; - - private void updateButtonCaption() - { - String buttonCaption = ""; - buttonCaption += Week.getFullDayName(Week.getWeekDayFromDate(lessonDate)); - buttonCaption += ", "; - buttonCaption += lessonDate.getFormattedString(); - registerEventManualDate.setText(buttonCaption); - - registerEventManualLesson.setText(R.string.dialog_event_manual_choose_lesson); - lessonStartTime = null; - - //lessonSubjectId = -1; - //lessonTeacherId = -1; - } - - private void updateSubjectTeacher() { - AsyncTask.execute(() -> { - Subject subject = app.db.subjectDao().getByIdNow(profileId, lessonSubjectId); - Teacher teacher = app.db.teacherDao().getByIdNow(profileId, lessonTeacherId); - - activity.runOnUiThread(() -> { - if (lessonTeacherId == -1 || teacher == null) - registerEventManualTeacher.setText(R.string.dialog_event_manual_no_teacher); - else - registerEventManualTeacher.setText(teacher.getFullName()); - - if (lessonSubjectId == -1 || subject == null) - registerEventManualSubject.setText(R.string.dialog_event_manual_no_subject); - else - registerEventManualSubject.setText(subject.longName); - }); - }); - - } - - private void addEvent() - { - registerEventManualDateLayout.setError(null); - registerEventManualLessonLayout.setError(null); - registerEventManualTeamLayout.setError(null); - registerEventManualTopicLayout.setError(null); - registerEventManualTypeLayout.setError(null); - boolean errors = false; - if (!lessonSelected) { - registerEventManualLessonLayout.setError(app.getString(R.string.dialog_event_manual_lesson_choose)); - errors = true; - } - if (registerEventManualShare.isChecked() && lessonTeamId == -1) { - registerEventManualTeamLayout.setError(app.getString(R.string.dialog_event_manual_team_choose)); - errors = true; - } - if (eventType == -2) { - registerEventManualTypeLayout.setError(app.getString(R.string.dialog_event_manual_type_choose)); - errors = true; - } - if (registerEventManualTopic.getText() == null || registerEventManualTopic.getText().toString().isEmpty()) { - registerEventManualTopicLayout.setError(app.getString(R.string.dialog_event_manual_topic_choose)); - errors = true; - } - if (errors) - return; - - long id = System.currentTimeMillis(); - Event event = new Event( - profileId, - id, - lessonDate, - lessonStartTime, - registerEventManualTopic.getText().toString(), - eventColor, - eventType, - true, - lessonTeacherId, - lessonSubjectId, - lessonTeamId - ); - Metadata metadata = new Metadata( - profileId, - eventType == TYPE_HOMEWORK ? Metadata.TYPE_HOMEWORK : Metadata.TYPE_EVENT, - event.id, - true, - true, - System.currentTimeMillis() - ); - if (editingEvent != null) { - event.id = editingEvent.id; - metadata.thingId = event.id; - metadata.addedDate = editingEvent.addedDate; - } - if (registerEventManualShare.isChecked()) { - if (profile.getRegistration() != REGISTRATION_ENABLED) { - new MaterialDialog.Builder(context) - .title(R.string.dialog_event_manual_must_register_title) - .content(R.string.dialog_event_manual_must_register_text) - .positiveText(R.string.i_agree) - .negativeText(R.string.no_thanks) - .neutralText(R.string.more) - .onPositive(((dialog1, which) -> { - profile.setRegistration(REGISTRATION_ENABLED); - app.profileSaveAsync(profile); - })) - .onNeutral(((dialog1, which) -> { - new MaterialDialog.Builder(context) - .title(R.string.help) - .content(app.getString(R.string.help_register_agreement)) - .positiveText(R.string.ok) - .show(); - })) - .show(); - return; - } - if (!profile.getEnableSharedEvents()) { - new MaterialDialog.Builder(context) - .title(R.string.dialog_event_manual_shared_disabled_title) - .content(R.string.dialog_event_manual_shared_disabled_text) - .positiveText(R.string.sure) - .negativeText(R.string.no_thanks) - .onPositive(((dialog1, which) -> { - profile.setEnableSharedEvents(true); - app.profileSaveAsync(profile); - })) - .show(); - return; - } - if (editingEvent != null - && editingEvent.sharedBy != null - && !editingEvent.sharedBy.equals("self")) { - // editing a shared event, and event was shared by someone else - // send a edit request - shareEvent(event, metadata, MODE_REQUEST); - } - else { - // editing a self-shared event, not-shared event or a new event, but want to share it - // post a new event to database - shareEvent(event, metadata, MODE_SHARE); - } - } - else if (editingEvent != null - && editingEvent.sharedBy != null - && editingEvent.sharedBy.equals("self")) { - // the event was self-shared, but needs to be removed now (from the server) - shareEvent(event, metadata, MODE_UNSHARE); - } - else { - // editing a local event - finishAdding(event, metadata); - } - } - - private static final int MODE_SHARE = 0; - private static final int MODE_UNSHARE = 1; - private static final int MODE_REQUEST = 2; - private static final int MODE_UNSHARE_REMOVE = 3; - private void shareEvent(Event event, Metadata metadata, int mode) { - AsyncTask.execute(() -> { - ServerRequest syncRequest = - new ServerRequest( - app, - app.requestScheme + APP_URL + "main.php?share", - "RegisterEventManualDialog/SH", - profile); - String teamUnshare = ""; - if (mode == MODE_SHARE) { - if (editingEvent != null - && event.teamId != editingEvent.teamId) { - // team ID was changed. Need to remove the event for other teams - teamUnshare = app.db.teamDao().getCodeByIdNow(profile.getId(), editingEvent.teamId); - syncRequest.setBodyParameter("team_unshare", teamUnshare); - } - syncRequest.setBodyParameter("event", app.gson.toJson(event)); - syncRequest.setBodyParameter("event_addedDate", Long.toString(metadata.addedDate)); - syncRequest.setBodyParameter("team", app.db.teamDao().getCodeByIdNow(profile.getId(), event.teamId)); - } - else if (mode == MODE_UNSHARE - && editingEvent != null) { - teamUnshare = app.db.teamDao().getCodeByIdNow(profile.getId(), editingEvent.teamId); - syncRequest.setBodyParameter("remove_id", Long.toString(editingEvent.id)); - syncRequest.setBodyParameter("team_unshare", teamUnshare); - } - else if (mode == MODE_UNSHARE_REMOVE - && editingEvent != null) { - teamUnshare = app.db.teamDao().getCodeByIdNow(profile.getId(), editingEvent.teamId); - syncRequest.setBodyParameter("remove_id", Long.toString(editingEvent.id)); - syncRequest.setBodyParameter("team_unshare", teamUnshare); - } - else if (mode == MODE_REQUEST) { - activity.runOnUiThread(() -> { - new MaterialDialog.Builder(context) - .title(R.string.error_occured) - .content(R.string.error_shared_edit_requests_not_implemented) - .positiveText(R.string.ok) - .show(); - }); - return; - } - - - activity.runOnUiThread(() -> { - progressDialog = new MaterialDialog.Builder(context) - .title(R.string.loading) - .content(R.string.sharing_event) - .progress(true, 0) - .canceledOnTouchOutside(false) - .show(); - }); - String finalTeamUnshare = teamUnshare; - - try { - JsonObject result = syncRequest.runSync(); - - activity.runOnUiThread(() -> progressDialog.dismiss()); - if (result == null) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(context) - .title(R.string.error_occured) - .content(R.string.sync_error_no_internet) - .positiveText(R.string.ok) - .show()); - - return; - } - if (result.get("success") == null || !result.get("success").getAsString().equals("true")) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(context) - .title(R.string.error_occured) - .content(R.string.sync_error_app_server) - .positiveText(R.string.ok) - .show()); - return; - } - if (mode == MODE_SHARE) { - event.sharedBy = "self"; - event.sharedByName = profile.getStudentNameLong(); - } - else if (mode == MODE_UNSHARE) { - event.sharedBy = null; - } - // check all profiles - // remove if necessary (team matching) - // this will hardly ever need to run - // (only if the user un-shares an event and has two profiles sharing the same team in common) - if (finalTeamUnshare != null && !finalTeamUnshare.equals("")) { - app.db.eventDao().removeByTeamId(editingEvent.teamId, editingEvent.id); - } - - if (mode == MODE_UNSHARE_REMOVE - && editingEvent != null) { - editingEvent.sharedBy = null; - AsyncTask.execute(() -> { - app.db.eventDao().remove(editingEvent); - }); - activity.runOnUiThread(() -> { - Toast.makeText(app, R.string.removed, Toast.LENGTH_SHORT).show(); - if (activity instanceof MainActivity) - ((MainActivity) activity).reloadTarget(); - dialog.dismiss(); - }); - } - else { - activity.runOnUiThread(() -> { - finishAdding(event, metadata); - }); - } - } catch (Exception e) { - activity.runOnUiThread(() -> { - // TODO show error in EventManualDialog - //app.apiEdziennik.guiShowErrorDialog(activity, new AppError(TAG, 379, CODE_OTHER, null, e), R.string.error_occured); - }); - } - }); - } - - private void finishAdding(Event event, Metadata metadata) { - AsyncTask.execute(() -> { - app.db.eventDao().add(event); - app.db.metadataDao().add(metadata); - if (editingEvent != null && (event.type == TYPE_HOMEWORK) != (editingEvent.type == TYPE_HOMEWORK)) { - // editingEvent's type changed - // the old metadata will not work because either the event changed *to* or *from* homework. - app.db.metadataDao().delete( - profileId, - editingEvent.type == TYPE_HOMEWORK ? Metadata.TYPE_HOMEWORK : Metadata.TYPE_EVENT, - editingEvent.id - ); - } - }); - Toast.makeText(app, R.string.saved, Toast.LENGTH_SHORT).show(); - dialog.dismiss(); - if (activity instanceof MainActivity) - ((MainActivity) activity).reloadTarget(); - } - - private DialogInterface.OnDismissListener dismissListener; - private boolean callDismissListener = true; - - public EventManualDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) { - this.dismissListener = dismissListener; - return this; - } - - private void performDismiss(DialogInterface dialogInterface) { - if (!dialog.isCancelled()) - dialog.dismiss(); - if (callDismissListener && dismissListener != null) { - dismissListener.onDismiss(dialogInterface); - } - callDismissListener = true; - } - - public static final int DIALOG_EVENT = 0; - public static final int DIALOG_HOMEWORK = 1; - - public void show(App _app, EventFull editingEvent, Date defaultDate, Time defaultTime, int dialogType) { - if (!(context instanceof Activity)) - return; - this.app = _app; - AsyncTask.execute(() -> { - this.profile = app.db.profileDao().getFullByIdNow(profileId); - if (profile != null) { - ((Activity) context).runOnUiThread(() -> { - actualShow(editingEvent, defaultDate, defaultTime, dialogType); - }); - } - else { - activity.runOnUiThread(() -> { - new MaterialDialog.Builder(activity) - .title(R.string.error_occured) - .content(R.string.sync_error_profile_not_found) - .positiveText(R.string.ok) - .show(); - }); - } - }); - } - private void actualShow(EventFull editingEvent, Date defaultDate, Time defaultTime, int dialogType) { - if (dialogType == DIALOG_HOMEWORK) { - eventType = TYPE_HOMEWORK; - eventColor = -1; - } - dialog = new MaterialDialog.Builder(context) - .title((dialogType == DIALOG_EVENT ? R.string.dialog_register_event_manual_title : R.string.dialog_register_homework_manual_title)) - .customView(R.layout.dialog_event_manual, true) - .positiveText(R.string.save) - .negativeText(R.string.cancel) - .autoDismiss(false) - .onPositive((dialog, which) -> addEvent()) - .onNegative((dialog, which) -> dialog.dismiss()) - .onNeutral((dialog, which) -> { - if (editingEvent != null) { - String sharedNotice = ""; - // notify the user that the event will be removed from all users - if (editingEvent.sharedBy != null - && editingEvent.sharedBy.equals("self")) { - sharedNotice += "\n\n"+context.getString(R.string.dialog_event_manual_remove_shared_self); - } - // notify the user that the event will be blacklisted and removed only locally - else if (editingEvent.sharedBy != null) { - sharedNotice += "\n\n"+context.getString(R.string.dialog_event_manual_remove_shared); - } - new MaterialDialog.Builder(context) - .title(R.string.are_you_sure) - .content(context.getString(R.string.dialog_register_event_manual_remove_confirmation)+sharedNotice) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive((dialog1, which1) -> { - if (editingEvent.sharedBy != null - && editingEvent.sharedBy.equals("self")) { - // editing event was self-shared - // the shared event needs to be unshared, then removed locally - shareEvent(editingEvent, null, MODE_UNSHARE_REMOVE); - return; - } - else if (editingEvent.sharedBy != null) { - // the event is shared by someone else - // blacklist it and remove only locally - AsyncTask.execute(() -> { - editingEvent.blacklisted = true; - app.db.eventDao().setBlacklisted(editingEvent.profileId, editingEvent.id, true); - }); - } - else { - // the event is not shared nor blacklisted - just remove it - AsyncTask.execute(() -> { - app.db.eventDao().remove(editingEvent); - }); - } - dialog.dismiss(); - dialog1.dismiss(); - Toast.makeText(app, R.string.removed, Toast.LENGTH_SHORT).show(); - if (activity instanceof MainActivity) - ((MainActivity) activity).reloadTarget(); - }) - .show(); - } - }) - .dismissListener((this::performDismiss)) - .show(); - - if (editingEvent != null) { - //&& (editingEvent.sharedBy == null || editingEvent.sharedBy.equals("self"))) { - dialog.setActionButton(DialogAction.NEUTRAL, R.string.remove); - } - - dialogView = dialog.getCustomView(); - assert dialogView != null; - - @ColorInt int primaryTextColor = Themes.INSTANCE.getPrimaryTextColor(context); - - registerEventManualDateLayout = dialogView.findViewById(R.id.registerEventManualDateLayout); - registerEventManualDate = dialogView.findViewById(R.id.registerEventManualDate); - registerEventManualDate.setCompoundDrawablesWithIntrinsicBounds(null, null, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_calendar).size(IconicsSize.dp(16)).color(IconicsColor.colorInt(primaryTextColor)), null); - //registerEventManualDate.setCompoundDrawablePadding(Utils.dpToPx(6)); - registerEventManualLessonLayout = dialogView.findViewById(R.id.registerEventManualLessonLayout); - registerEventManualLesson = dialogView.findViewById(R.id.registerEventManualLesson); - registerEventManualTeamLayout = dialogView.findViewById(R.id.registerEventManualTeamLayout); - registerEventManualTeam = dialogView.findViewById(R.id.registerEventManualTeam); - registerEventManualTeacher = dialogView.findViewById(R.id.registerEventManualTeacher); - registerEventManualSubject = dialogView.findViewById(R.id.registerEventManualSubject); - registerEventManualTypeLayout = dialogView.findViewById(R.id.registerEventManualTypeLayout); - registerEventManualType = dialogView.findViewById(R.id.registerEventManualType); - registerEventManualTopicLayout = dialogView.findViewById(R.id.registerEventManualTopicLayout); - registerEventManualTopic = dialogView.findViewById(R.id.registerEventManualTopic); - registerEventManualShare = dialogView.findViewById(R.id.registerEventManualShare); - registerEventManualShareText = dialogView.findViewById(R.id.registerEventManualShareText); - registerEventManualShare.setOnCheckedChangeListener(((buttonView, isChecked) -> { - // show notice text if share is enabled or event is already shared - registerEventManualShareText.setVisibility(isChecked || (editingEvent != null && editingEvent.sharedBy != null) ? View.VISIBLE : View.GONE); - // notify the user if he wants to share the event with no team selected - if (isChecked && lessonTeamId == -1) { - Toast.makeText(context, R.string.dialog_event_manual_cannot_share, Toast.LENGTH_SHORT).show(); - } - if (editingEvent != null && editingEvent.sharedBy != null) { - // if the event is already shared, show the (change) or (removal) notice text - if (isChecked) - registerEventManualShareText.setText(R.string.dialog_event_manual_share_will_change); - else - registerEventManualShareText.setText(R.string.dialog_event_manual_share_will_remove); - } - })); - - - registerEventManualDate.setOnClickListener(v -> DatePickerDialog.newInstance( - (view, year, monthOfYear, dayOfMonth) -> { - lessonDate.year = year; - lessonDate.month = monthOfYear + 1; - lessonDate.day = dayOfMonth; - updateButtonCaption(); - }, - lessonDate.year, - lessonDate.month - 1, - lessonDate.day - ).show(((AppCompatActivity) context).getSupportFragmentManager(), "DatePickerDialog")); - - registerEventManualLesson.setOnClickListener(v -> { - app.db.lessonDao().getAllByDateWithoutChanges(profileId, lessonDate).observe(activity, new Observer>() { - @Override - public void onChanged(@Nullable List lessons) { - PopupMenu popup = new PopupMenu(context, registerEventManualLesson); - - if (lessons != null && lessons.size() != 0) { - for (LessonFull lesson: lessons) { - int index = lessons.indexOf(lesson); - popup.getMenu().add( - 0, - index, - index+1, - lesson.startTime.getStringHM()+" "+bs(lesson.subjectLongName)); - } - } - popup.getMenu().add(1, -1, 0, R.string.dialog_event_manual_all_day); - popup.getMenu().add(1, 0, 100, R.string.dialog_event_manual_custom_time); - - popup.setOnMenuItemClickListener(item -> { - if (lessons != null && item.getGroupId() == 0) { - LessonFull lesson = lessons.get(item.getItemId()); - lessonTeacherId = lesson.teacherId; - lessonStartTime = lesson.startTime; - lessonSubjectId = lesson.subjectId; - registerEventManualTeacher.setText(bs(lesson.teacherFullName)); - registerEventManualSubject.setText(bs(lesson.subjectLongName)); - lessonTeamId = lesson.teamId; - registerEventManualTeam.setText(ns(context.getString(R.string.dialog_event_manual_no_team), bs(lesson.teamName))); - registerEventManualLesson.setText(item.getTitle()); - lessonSelected = true; - } - else if (item.getGroupId() == 1) { - if (item.getItemId() == -1) { - lessonStartTime = null; - lessonTeacherId = -1; - lessonSubjectId = -1; - if (editingEvent != null) { - lessonTeacherId = editingEvent.teacherId; - lessonSubjectId = editingEvent.subjectId; - } - updateSubjectTeacher(); - registerEventManualLesson.setText(item.getTitle()); - lessonSelected = true; - return true; - } - Time now = Time.getNow(); - TimePickerDialog.newInstance((view, hourOfDay, minute, second) -> { - lessonStartTime = new Time(hourOfDay, minute, second); - lessonTeacherId = -1; - lessonSubjectId = -1; - if (editingEvent != null) { - lessonTeacherId = editingEvent.teacherId; - lessonSubjectId = editingEvent.subjectId; - } - updateSubjectTeacher(); - //lessonTeamId = -1; - registerEventManualLesson.setText(lessonStartTime.getStringHM()); - lessonSelected = true; - }, now.hour, now.minute, 0, true).show(((AppCompatActivity) context).getSupportFragmentManager(), "TimePickerDialog"); - } - return true; - }); - - popup.show(); - app.db.lessonDao().getAllByDateWithoutChanges(profileId, lessonDate).removeObserver(this); - } - }); - }); - - registerEventManualTeam.setOnClickListener(v -> { - app.db.teamDao().getAll(profileId).observe(activity, new Observer>() { - @Override - public void onChanged(@Nullable List teams) { - PopupMenu popup = new PopupMenu(context, registerEventManualTeam); - if (teams != null && teams.size() != 0) { - int index = 0; - for (Team team : teams) { - popup.getMenu().add(0, index++, (index)+1, team.name); - } - } - popup.getMenu().add(1, 0, 100, context.getString(R.string.dialog_event_manual_no_team)); - popup.setOnMenuItemClickListener(item -> { - long id = -1; - if (item.getGroupId() == 0 && teams != null) { - Team team = teams.get(item.getItemId()); - id = team.id; - } - lessonTeamId = id; - registerEventManualTeam.setText(item.getTitle()); - return true; - }); - popup.show(); - app.db.teamDao().getAll(profileId).removeObserver(this); - } - }); - }); - - registerEventManualTeacher.setOnClickListener(v -> { - app.db.teacherDao().getAllTeachers(profileId).observe(activity, new Observer>() { - @Override - public void onChanged(@Nullable List teachers) { - PopupMenu popup = new PopupMenu(context, registerEventManualTeacher); - if (teachers != null && teachers.size() != 0) { - int index = 0; - for (Teacher teacher : teachers) { - popup.getMenu().add(0, index++, (index)+1, teacher.getFullName()); - } - } - popup.getMenu().add(1, 0, 0, context.getString(R.string.dialog_event_manual_no_teacher)); - popup.setOnMenuItemClickListener(item -> { - long id = -1; - if (item.getGroupId() == 0 && teachers != null) { - Teacher teacher = teachers.get(item.getItemId()); - id = teacher.id; - } - lessonTeacherId = id; - registerEventManualTeacher.setText(item.getTitle()); - return true; - }); - popup.show(); - app.db.teacherDao().getAllTeachers(profileId).removeObserver(this); - } - }); - }); - registerEventManualSubject.setOnClickListener(v -> { - app.db.subjectDao().getAll(profileId).observe(activity, new Observer>() { - @Override - public void onChanged(@Nullable List subjects) { - PopupMenu popup = new PopupMenu(context, registerEventManualSubject); - if (subjects != null && subjects.size() != 0) { - int index = 0; - for (Subject subject: subjects) { - popup.getMenu().add(0, index++, (index)+1, subject.longName); - } - } - popup.getMenu().add(1, 0, 0, context.getString(R.string.dialog_event_manual_no_subject)); - popup.setOnMenuItemClickListener(item -> { - long id = -1; - if (item.getGroupId() == 0 && subjects != null) { - Subject subject = subjects.get(item.getItemId()); - id = subject.id; - } - lessonSubjectId = id; - registerEventManualSubject.setText(item.getTitle()); - return true; - }); - popup.show(); - app.db.subjectDao().getAll(profileId).removeObserver(this); - } - }); - }); - registerEventManualType.setOnClickListener(v -> { - app.db.eventTypeDao().getAll(profileId).observe(activity, new Observer>() { - @Override - public void onChanged(@Nullable List eventTypes) { - PopupMenu popup = new PopupMenu(context, registerEventManualType); - if (eventTypes != null && eventTypes.size() != 0) { - int index = 0; - for (EventType eventType : eventTypes) { - popup.getMenu().add(0, index++, (index)+1, eventType.name); - } - } - popup.setOnMenuItemClickListener(item -> { - if (item.getGroupId() == 0 && eventTypes != null) { - EventType typeObj = eventTypes.get(item.getItemId()); - eventType = (int)typeObj.id; - eventColor = -1; // set -1 as it's the event type's default color - registerEventManualColorPreview.setBackgroundColor(typeObj.color); // set event type's color here to show how will it look - } - registerEventManualType.setText(item.getTitle()); - return true; - }); - popup.show(); - app.db.eventTypeDao().getAll(profileId).removeObserver(this); - } - }); - }); - - View.OnClickListener colorChooseOnClick = (v -> { - AsyncTask.execute(() -> { - EventType type = app.db.eventTypeDao().getByIdNow(profileId, eventType); - activity.runOnUiThread(() -> { - ColorPickerDialog colorPickerDialog = ColorPickerDialog.newBuilder() - .setColor(eventColor != -1 ? eventColor : type != null ? type.color : COLOR_DEFAULT) - .create(); - colorPickerDialog.setColorPickerDialogListener( - new ColorPickerDialogListener() { - @Override - public void onColorSelected(int dialogId, int color) { - registerEventManualColorPreview.setBackgroundColor(color); - eventColor = color; - } - - @Override - public void onDialogDismissed(int dialogId) { - - } - }); - colorPickerDialog.show(((Activity)context).getFragmentManager(), "color-picker-dialog"); - }); - }); - }); - - registerEventManualColorPreview = dialogView.findViewById(R.id.registerEventManualColorPreview); - registerEventManualColorPreview.setOnClickListener(colorChooseOnClick); - registerEventManualColorChoose = dialogView.findViewById(R.id.registerEventManualColorChoose); - registerEventManualColorChoose.setOnClickListener(colorChooseOnClick); - - if (eventType != TYPE_UNDEFINED) { - AsyncTask.execute(() -> { - EventType type = app.db.eventTypeDao().getByIdNow(profileId, eventType); - activity.runOnUiThread(() -> { - if (type != null) { - eventColor = -1; - registerEventManualColorPreview.setBackgroundColor(type.color); - registerEventManualType.setText(type.name); - } - else { - eventColor = -1; - eventType = TYPE_UNDEFINED; - registerEventManualColorPreview.setBackgroundColor(COLOR_DEFAULT); - registerEventManualType.setText(""); - } - }); - }); - } - else { - registerEventManualColorPreview.setBackgroundColor(COLOR_DEFAULT); - eventColor = -1; - } - - /*if (dialogType == DIALOG_HOMEWORK) { - dialogView.findViewById(R.id.registerEventManualColorContainer).setVisibility(View.GONE); - registerEventManualType.setVisibility(View.GONE); - eventType = TYPE_HOMEWORK; - } - else { - - - }*/ - - updateButtonCaption(); - - if (editingEvent != null) { - this.editingEvent = editingEvent; - lessonDate = editingEvent.eventDate.clone(); - lessonTeamId = editingEvent.teamId; - // check the (shared) checkbox if the event is already shared by someone - registerEventManualShare.setChecked(editingEvent.sharedBy != null); - // .. but disable the ability to un-share, if it's shared by someone else - registerEventManualShare.setEnabled(editingEvent.sharedBy == null || editingEvent.sharedBy.equals("self")); - registerEventManualShare.jumpDrawablesToCurrentState(); - // show the notice text if the event is already shared - registerEventManualShareText.setVisibility(editingEvent.sharedBy != null ? View.VISIBLE : View.GONE); - if (editingEvent.sharedBy != null) { - // show the (change) notice if the event is shared by (self) - if (editingEvent.sharedBy.equals("self")) - registerEventManualShareText.setText(R.string.dialog_event_manual_share_will_change); - // otherwise show the (edit request) notice - else - registerEventManualShareText.setText(app.getString(R.string.dialog_event_manual_share_will_request, editingEvent.sharedByName)); - } - - updateButtonCaption(); // updates the lesson date and clears the time - - lessonSubjectId = editingEvent.subjectId; - lessonTeacherId = editingEvent.teacherId; - lessonStartTime = editingEvent.startTime == null ? null : editingEvent.startTime.clone(); - lessonSelected = true; - - registerEventManualTeam.setText(ns(context.getString(R.string.dialog_event_manual_no_team), editingEvent.teamName)); - registerEventManualTopic.setText(editingEvent.topic); - registerEventManualType.setText(editingEvent.typeName); - eventColor = editingEvent.getColor(); - eventType = editingEvent.type; - registerEventManualColorPreview.setBackgroundColor(eventColor); - - if (lessonSubjectId == -1) { - //lessonTeacherId = -1; // why tho? - registerEventManualLesson.setText(lessonStartTime == null ? app.getString(R.string.dialog_event_manual_all_day) : lessonStartTime.getStringHM()); - } - else { - registerEventManualLesson.setText((lessonStartTime == null ? app.getString(R.string.dialog_event_manual_all_day) + "," : lessonStartTime.getStringHM())+" "+bs(editingEvent.subjectLongName)); - } - } - else { - if (defaultDate != null) { - lessonDate = defaultDate.clone(); - updateButtonCaption(); - } - if (defaultDate != null && defaultTime != null) { // ONLY used when adding an event to the previously selected lesson - AsyncTask.execute(() -> { - LessonFull lesson = app.db.lessonDao().getByDateTimeWithoutChangesNow(profileId, defaultDate, defaultTime); - - lessonTeacherId = lesson.teacherId; - lessonStartTime = lesson.startTime.clone(); - lessonTeamId = lesson.teamId; - lessonSubjectId = lesson.subjectId; - lessonSelected = true; - activity.runOnUiThread(() -> { - registerEventManualTeam.setText(ns(context.getString(R.string.dialog_event_manual_no_team), lesson.teamName)); - registerEventManualLesson.setText(lesson.startTime.getStringHM()+" "+bs(lesson.subjectLongName)); - registerEventManualTeacher.setText(ns(context.getString(R.string.dialog_event_manual_no_teacher), lesson.teacherFullName)); - registerEventManualSubject.setText(ns(context.getString(R.string.dialog_event_manual_no_subject), lesson.subjectLongName)); - }); - }); - } - else { - AsyncTask.execute(() -> { - Team team = app.db.teamDao().getClassNow(profileId); - if (team != null) { - activity.runOnUiThread(() -> { - lessonTeamId = team.id; - registerEventManualTeam.setText(ns(context.getString(R.string.dialog_event_manual_no_team), team.name)); - }); - } - }); - } - } - updateSubjectTeacher(); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt new file mode 100644 index 00000000..06b5f1a6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -0,0 +1,613 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-12. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL +import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.jaredrummler.android.colorpicker.ColorPickerDialog +import com.jaredrummler.android.colorpicker.ColorPickerDialogListener +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.db.entity.Event +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.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding +import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationEnableDialog +import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS +import pl.szczodrzynski.edziennik.utils.Anim +import pl.szczodrzynski.edziennik.utils.TextInputDropDown +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.coroutines.CoroutineContext + +class EventManualDialog( + val activity: AppCompatActivity, + val profileId: Int, + val defaultLesson: LessonFull? = null, + val defaultDate: Date? = null, + val defaultTime: Time? = null, + val defaultType: Long? = null, + val editingEvent: EventFull? = null, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + + companion object { + private const val TAG = "EventManualDialog" + } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val app by lazy { activity.application as App } + private lateinit var b: DialogEventManualV2Binding + private lateinit var dialog: AlertDialog + private var profile: Profile? = null + + private var customColor: Int? = null + private val editingShared = editingEvent?.sharedBy != null + private val editingOwn = editingEvent?.sharedBy == "self" + private var removeEventDialog: AlertDialog? = null + + private val api by lazy { + SzkolnyApi(app) + } + + private var enqueuedWeekDialog: AlertDialog? = null + private var enqueuedWeekStart = Date.getToday() + + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + EventBus.getDefault().register(this) + b = DialogEventManualV2Binding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_event_manual_title) + .setView(b.root) + .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.save, null) + .apply { + if (editingEvent != null) { + setNeutralButton(R.string.remove, null) + } + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + EventBus.getDefault().unregister(this@EventManualDialog) + enqueuedWeekDialog?.dismiss() + progressDialog?.dismiss() + } + .setCancelable(false) + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + saveEvent() + } + + val neutralButton = dialog.getButton(BUTTON_NEUTRAL) + neutralButton?.setOnClickListener { + showRemoveEventDialog() + } + } + + show() + } + + b.shareSwitch.isChecked = editingShared + b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn) + + b.showMore.onClick { // TODO iconics is broken + it.apply { + refreshDrawableState() + + if (isChecked) + Anim.expand(b.moreLayout, 200, null) + else + Anim.collapse(b.moreLayout, 200, null) + } + } + + updateShareText() + b.shareSwitch.onChange { _, isChecked -> + updateShareText(isChecked) + } + + loadLists() + }} + + private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) { + b.shareDetails.visibility = if (checked || editingShared) + View.VISIBLE + else View.GONE + + val text = when { + checked && editingShared && editingOwn -> R.string.dialog_event_manual_share_will_change + checked && editingShared -> R.string.dialog_event_manual_share_will_request + !checked && editingShared -> R.string.dialog_event_manual_share_will_remove + else -> R.string.dialog_event_manual_share_first_notice + } + + b.shareDetails.setText(text, editingEvent?.sharedByName ?: "") + } + + private fun syncTimetable(date: Date) { + if (enqueuedWeekDialog != null) { + return + } + if (app.profile.getStudentData("timetableNotPublic", false)) { + return + } + val weekStart = date.weekStart + enqueuedWeekDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.timetable_syncing_text) + .setCancelable(false) + .show() + + enqueuedWeekStart = weekStart + + EdziennikTask.syncProfile( + profileId = profileId, + viewIds = listOf( + MainActivity.DRAWER_ITEM_TIMETABLE to 0 + ), + arguments = JsonObject( + "weekStart" to weekStart.stringY_m_d + ) + ).enqueue(activity) + } + + private fun showSharingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_sharing_text) + .setCancelable(false) + .show() + } + + private fun showRemovingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_removing_text) + .setCancelable(false) + .show() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + if (event.profileId == profileId) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + launch { + b.timeDropdown.loadItems() + b.timeDropdown.selectDefault(editingEvent?.time) + b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime) + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + } + + private fun loadLists() { launch { + profile = withContext(Dispatchers.Default) { app.db.profileDao().getByIdNow(profileId) } + + with (b.dateDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showWeekDays = false + showDays = true + showOtherDate = true + defaultLesson?.let { + nextLessonSubjectId = it.displaySubjectId + nextLessonSubjectName = it.displaySubjectName + nextLessonTeamId = it.displayTeamId + } + loadItems() + selectDefault(editingEvent?.date) + selectDefault(defaultLesson?.displayDate ?: defaultDate) + onDateSelected = { date, lesson -> + b.timeDropdown.deselect() + b.timeDropdown.lessonsDate = date + this@EventManualDialog.launch { + if (!b.timeDropdown.loadItems()) + syncTimetable(date) + lesson?.displayStartTime?.let { b.timeDropdown.selectTime(it) } + lesson?.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect() + lesson?.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect() + lesson?.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass() + } + } + } + + with (b.timeDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showAllDay = true + showCustomTime = true + lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday() + displayMode = DISPLAY_LESSONS + if (!loadItems()) + syncTimetable(lessonsDate ?: Date.getToday()) + selectDefault(editingEvent?.time) + if (editingEvent != null && editingEvent.time == null) + select(0L) + selectDefault(defaultLesson?.displayStartTime ?: defaultTime) + onLessonSelected = { lesson -> + lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect() + lesson.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect() + lesson.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass() + } + } + + with (b.teamDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoTeam = true + loadItems() + selectTeamClass() + selectDefault(editingEvent?.teamId) + selectDefault(defaultLesson?.displayTeamId) + } + + with (b.subjectDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoSubject = true + showCustomSubject = false + loadItems() + selectDefault(editingEvent?.subjectId) + selectDefault(defaultLesson?.displaySubjectId) + } + + with (b.teacherDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoTeacher = true + loadItems() + selectDefault(editingEvent?.teacherId) + selectDefault(defaultLesson?.displayTeacherId) + } + + + val deferred = async(Dispatchers.Default) { + // get the event type list + var eventTypes = app.db.eventTypeDao().getAllNow(profileId) + + if (eventTypes.none { it.id in -1L..10L }) { + eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId) + } + + b.typeDropdown.clear() + b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) } + } + deferred.await() + + b.typeDropdown.isEnabled = true + + defaultType?.let { + b.typeDropdown.select(it) + } + + b.typeDropdown.selected?.let { item -> + customColor = (item.tag as EventType).color + } + + // copy IDs from event being edited + editingEvent?.let { + b.topic.setText(it.topic) + b.typeDropdown.select(it.type)?.let { item -> + customColor = (item.tag as EventType).color + } + if (it.color != null && it.color != -1) + customColor = it.color + } + + // copy IDs from the LessonFull + defaultLesson?.let { + b.teamDropdown.select(it.displayTeamId) + } + + b.typeDropdown.setOnChangeListener { + b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP) + customColor = null + return@setOnChangeListener true + } + + (customColor ?: Event.COLOR_DEFAULT).let { + b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP) + } + + b.typeColor.onClick { + val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT + val colorPickerDialog = ColorPickerDialog.newBuilder() + .setColor(currentColor) + .create() + colorPickerDialog.setColorPickerDialogListener( + object : ColorPickerDialogListener { + override fun onDialogDismissed(dialogId: Int) {} + override fun onColorSelected(dialogId: Int, color: Int) { + b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) + customColor = color + } + }) + colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog") + } + }} + + private fun showRemoveEventDialog() { + val shareNotice = when { + editingShared && editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self) + editingShared && !editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared) + else -> "" + } + removeEventDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(activity.getString(R.string.dialog_register_event_manual_remove_confirmation)+shareNotice) + .setPositiveButton(R.string.yes, null) + .setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() } + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + removeEvent() + } + } + + show() + } + } + + private fun saveEvent() { + val date = b.dateDropdown.getSelected() as? Date + val timeSelected = b.timeDropdown.getSelected() + val teamId = b.teamDropdown.getSelected() as? Long + val type = b.typeDropdown.selected?.id + val topic = b.topic.text?.toString() + val subjectId = b.subjectDropdown.getSelected() as? Long + val teacherId = b.teacherDropdown.getSelected() + + val share = b.shareSwitch.isChecked + + if (share && profile?.registration != Profile.REGISTRATION_ENABLED) { + RegistrationEnableDialog(activity, profileId).showEventShareDialog { + if (it != null) + profile = it + saveEvent() + } + return + } + + b.dateDropdown.error = null + b.teamDropdown.error = null + b.typeDropdown.error = null + b.topic.error = null + + var isError = false + + if (date == null) { + b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose) + b.dateDropdown.requestFocus() + isError = true + } + + if (timeSelected !is Pair<*, *> && timeSelected != 0L) { + b.timeDropdown.error = app.getString(R.string.dialog_event_manual_time_choose) + if (!isError) b.timeDropdown.parent.requestChildFocus(b.timeDropdown, b.timeDropdown) + isError = true + } + + if (share && teamId == null) { + b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) + if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown) + isError = true + } + + if (type == null) { + b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose) + if (!isError) b.typeDropdown.requestFocus() + isError = true + } + + if (topic.isNullOrBlank()) { + b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose) + if (!isError) b.topic.requestFocus() + isError = true + } + + val startTime = if (timeSelected == 0L) + null + else + (timeSelected as? Pair<*, *>)?.first as? Time + + if (isError) return + date ?: return + topic ?: return + + val id = System.currentTimeMillis() + + val eventObject = Event( + profileId = profileId, + id = editingEvent?.id ?: id, + date = date, + time = startTime, + topic = topic, + color = customColor, + type = type ?: Event.TYPE_DEFAULT, + teacherId = teacherId ?: -1, + subjectId = subjectId ?: -1, + teamId = teamId ?: -1, + addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() + ).also { + it.addedManually = true + } + + val metadataObject = Metadata( + profileId, + when (type) { + Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK + else -> Metadata.TYPE_EVENT + }, + eventObject.id, + true, + true + ) + + launch { + val profile = app.db.profileDao().getByIdNow(profileId) + + if (!share && !editingShared) { + //Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show() + finishAdding(eventObject, metadataObject) + } + else if (editingShared && !editingOwn) { + Toast.makeText(activity, "Opcja edycji wydarzeń innych uczniów nie została jeszcze zaimplementowana.", Toast.LENGTH_LONG).show() + // TODO + } + else if (!share && editingShared) { + showSharingProgressDialog() + + eventObject.apply { + sharedBy = null + sharedByName = profile?.studentNameLong + } + + api.runCatching(activity) { + unshareEvent(eventObject) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + eventObject.sharedByName = null + finishAdding(eventObject, metadataObject) + } + else if (share) { + showSharingProgressDialog() + + eventObject.apply { + sharedBy = profile?.userCode + sharedByName = profile?.studentNameLong + addedDate = System.currentTimeMillis() + } + + api.runCatching(activity) { + shareEvent(eventObject.withMetadata(metadataObject)) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + eventObject.sharedBy = "self" + finishAdding(eventObject, metadataObject) + } + else { + Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show() + } + progressDialog?.dismiss() + } + } + + private fun removeEvent() { + launch { + if (editingShared && editingOwn) { + // unshare + remove own event + showRemovingProgressDialog() + + api.runCatching(activity) { + unshareEvent(editingEvent!!) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + finishRemoving() + } else if (editingShared && !editingOwn) { + // remove + blacklist somebody's event + Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show() + // TODO + } else { + // remove event + //Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() + finishRemoving() + } + progressDialog?.dismiss() + } + } + + private fun finishAdding(eventObject: Event, metadataObject: Metadata) { + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().upsert(eventObject) + app.db.metadataDao().add(metadataObject) + } + } + + dialog.dismiss() + Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } + private fun finishRemoving() { + editingEvent ?: return + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().remove(editingEvent) + } + } + + removeEventDialog?.dismiss() + dialog.dismiss() + Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt deleted file mode 100644 index 1d63ee69..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2019-11-12. - */ - -package pl.szczodrzynski.edziennik.ui.dialogs.event - -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer -import com.google.android.material.datepicker.MaterialDatePicker -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.* -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.db.modules.events.Event -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team -import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson -import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull -import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding -import pl.szczodrzynski.edziennik.utils.TextInputDropDown -import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time -import pl.szczodrzynski.edziennik.utils.models.Week -import kotlin.coroutines.CoroutineContext - -class EventManualV2Dialog( - val activity: AppCompatActivity, - val profileId: Int, - val defaultLesson: LessonFull? = null, - val defaultDate: Date? = null, - val defaultTime: Time? = null, - val defaultType: Int? = null, - val editingEvent: Event? = null, - val onShowListener: ((tag: String) -> Unit)? = null, - val onDismissListener: ((tag: String) -> Unit)? = null -) : CoroutineScope { - - companion object { - private const val TAG = "EventManualDialog" - } - - private lateinit var job: Job - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - private val app by lazy { activity.application as App } - private lateinit var b: DialogEventManualV2Binding - private lateinit var dialog: AlertDialog - private lateinit var event: Event - private var defaultLoaded = false - - init { run { - if (activity.isFinishing) - return@run - job = Job() - onShowListener?.invoke(TAG) - b = DialogEventManualV2Binding.inflate(activity.layoutInflater) - dialog = MaterialAlertDialogBuilder(activity) - .setTitle(R.string.dialog_event_manual_title) - .setView(b.root) - .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } - .setPositiveButton(R.string.save) { _, _ -> saveEvent() } - .setOnDismissListener { - onDismissListener?.invoke(TAG) - } - .show() - - event = editingEvent?.clone() ?: Event().also { event -> - event.profileId = profileId - /*defaultDate?.let { - event.eventDate = it - b.date = it - } - defaultTime?.let { - event.startTime = it - b.time = it - } - defaultType?.let { - event.type = it - }*/ - } - - loadLists() - }} - - private fun loadLists() { launch { - val deferred = async(Dispatchers.Default) { - // get the team list - val teams = app.db.teamDao().getAllNow(profileId) - b.teamDropdown.clear() - b.teamDropdown += TextInputDropDown.Item( - -1, - activity.getString(R.string.dialog_event_manual_no_team), - "" - ) - b.teamDropdown += teams.map { TextInputDropDown.Item(it.id, it.name, tag = it) } - - // get the subject list - val subjects = app.db.subjectDao().getAllNow(profileId) - b.subjectDropdown.clear() - b.subjectDropdown += TextInputDropDown.Item( - -1, - activity.getString(R.string.dialog_event_manual_no_subject), - "" - ) - b.subjectDropdown += subjects.map { TextInputDropDown.Item(it.id, it.longName, tag = it) } - - // get the teacher list - val teachers = app.db.teacherDao().getAllNow(profileId) - b.teacherDropdown.clear() - b.teacherDropdown += TextInputDropDown.Item( - -1, - activity.getString(R.string.dialog_event_manual_no_teacher), - "" - ) - b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) } - } - deferred.await() - - b.teamDropdown.isEnabled = true - b.subjectDropdown.isEnabled = true - b.teacherDropdown.isEnabled = true - - // copy IDs from event being edited - editingEvent?.let { - b.teamDropdown.select(it.teamId) - b.subjectDropdown.select(it.subjectId) - b.teacherDropdown.select(it.teacherId) - } - - // copy IDs from the LessonFull - defaultLesson?.let { - b.teamDropdown.select(it.displayTeamId) - b.subjectDropdown.select(it.displaySubjectId) - b.teacherDropdown.select(it.displayTeacherId) - } - - loadDates() - }} - - private fun loadDates() { launch { - val date = Date.getToday() - val today = date.value - var weekDay = date.weekDay - - val deferred = async(Dispatchers.Default) { - val dates = mutableListOf() - // item choosing the next lesson of specific subject - b.subjectDropdown.selected?.let { - if (it.tag is Subject) { - dates += TextInputDropDown.Item( - -it.id, - activity.getString(R.string.dialog_event_manual_date_next_lesson, it.tag.longName) - ) - } - } - - // TODAY - dates += TextInputDropDown.Item( - date.value.toLong(), - activity.getString(R.string.dialog_event_manual_date_today, date.formattedString), - tag = date.clone() - ) - - // TOMORROW - if (weekDay < 4) { - date.stepForward(0, 0, 1) - weekDay++ - dates += TextInputDropDown.Item( - date.value.toLong(), - activity.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString), - tag = date.clone() - ) - } - // REMAINING SCHOOL DAYS OF THE CURRENT WEEK - while (weekDay < 4) { - date.stepForward(0, 0, 1) // step one day forward - weekDay++ - dates += TextInputDropDown.Item( - date.value.toLong(), - activity.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString), - tag = date.clone() - ) - } - // go to next week Monday - date.stepForward(0, 0, -weekDay + 7) - weekDay = 0 - // ALL SCHOOL DAYS OF THE NEXT WEEK - while (weekDay < 4) { - dates += TextInputDropDown.Item( - date.value.toLong(), - activity.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString), - tag = date.clone() - ) - date.stepForward(0, 0, 1) // step one day forward - weekDay++ - } - dates += TextInputDropDown.Item( - -1L, - activity.getString(R.string.dialog_event_manual_date_other) - ) - dates - } - - val dates = deferred.await() - b.dateDropdown.clear().append(dates) - - editingEvent?.let { - b.dateDropdown.select(it.eventDate.value.toLong()) - } - - defaultLesson?.let { - b.dateDropdown.select(it.displayDate?.value?.toLong()) - } - - if (b.dateDropdown.selected == null) { - b.dateDropdown.select(today.toLong()) - } - - b.dateDropdown.isEnabled = true - - b.dateDropdown.setOnChangeListener { item -> - when { - // next lesson with specified subject - item.id < -1 -> { - app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer { - val lessonDate = it?.displayDate ?: return@Observer - b.dateDropdown.selected = TextInputDropDown.Item( - lessonDate.value.toLong(), - lessonDate.formattedString, - tag = lessonDate - ) - // TODO load correct hour when selecting next lesson - b.dateDropdown.updateText() - it.let { - b.teamDropdown.select(it.displayTeamId) - b.subjectDropdown.select(it.displaySubjectId) - b.teacherDropdown.select(it.displayTeacherId) - } - defaultLoaded = false - loadHours() - }) - return@setOnChangeListener false - } - // custom date - item.id == -1L -> { - MaterialDatePicker.Builder - .datePicker() - .setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: Date.getToday()).inMillis) - .build() - .apply { - addOnPositiveButtonClickListener { - val dateSelected = Date.fromMillis(it) - b.dateDropdown.selected = TextInputDropDown.Item( - dateSelected.value.toLong(), - dateSelected.formattedString, - tag = dateSelected - ) - b.dateDropdown.updateText() - loadHours() - } - show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker") - } - - return@setOnChangeListener false - } - // a specific date - else -> { - b.dateDropdown.select(item) - loadHours() - } - } - return@setOnChangeListener true - } - - loadHours() - }} - - private fun loadHours() { - b.timeDropdown.isEnabled = false - // get the selected date - val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return - // get all lessons for selected date - app.db.timetableDao().getForDate(profileId, date).observeOnce(activity, Observer { lessons -> - val hours = mutableListOf() - // add All day time choice - hours += TextInputDropDown.Item( - 0L, - activity.getString(R.string.dialog_event_manual_all_day) - ) - lessons.forEach { lesson -> - if (lesson.type == Lesson.TYPE_NO_LESSONS) { - // indicate there are no lessons this day - hours += TextInputDropDown.Item( - -2L, - activity.getString(R.string.dialog_event_manual_no_lessons) - ) - return@forEach - } - // create the lesson caption - val text = listOfNotEmpty( - lesson.displayStartTime?.stringHM ?: "", - lesson.displaySubjectName?.let { - when { - lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable() - lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable() - else -> it - } - } ?: "" - ) - // add an item with LessonFull as the tag - hours += TextInputDropDown.Item( - lesson.displayStartTime?.value?.toLong() ?: -1, - text.concat(" "), - tag = lesson - ) - } - b.timeDropdown.clear().append(hours) - - if (defaultLoaded) { - b.timeDropdown.deselect() - // select the TEAM_CLASS if possible - b.teamDropdown.items.singleOrNull { - it.tag is Team && it.tag.type == Team.TYPE_CLASS - }?.let { - b.teamDropdown.select(it) - } ?: b.teamDropdown.deselect() - - // clear subject, teacher selection - b.subjectDropdown.deselect() - b.teacherDropdown.deselect() - } - else { - editingEvent?.let { - b.timeDropdown.select(it.startTime?.value?.toLong()) - } - - defaultLesson?.let { - b.timeDropdown.select(it.displayStartTime?.value?.toLong()) - } - } - defaultLoaded = true - b.timeDropdown.isEnabled = true - - // attach a listener to time dropdown - b.timeDropdown.setOnChangeListener { item -> - when { - // custom start hour - item.id == -1L -> { - - return@setOnChangeListener false - } - // no lessons this day - item.id == -2L -> { - b.timeDropdown.deselect() - return@setOnChangeListener false - } - // selected a specific lesson - else -> { - if (item.tag is LessonFull) { - // update team, subject, teacher dropdowns, - // using the LessonFull from item tag - b.teamDropdown.deselect() - b.subjectDropdown.deselect() - b.teacherDropdown.deselect() - item.tag.displayTeamId?.let { - b.teamDropdown.select(it) - } - item.tag.displaySubjectId?.let { - b.subjectDropdown.select(it) - } - item.tag.displayTeacherId?.let { - b.teacherDropdown.select(it) - } - } - } - } - return@setOnChangeListener true - } - }) - } - - private fun saveEvent() { - - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.java deleted file mode 100644 index cabc3e63..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.dialogs.grade; - -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; -import android.view.View; - -import androidx.core.graphics.ColorUtils; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.afollestad.materialdialogs.MaterialDialog; - -import java.text.DecimalFormat; -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListAdapter; -import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding; -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade; -import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeFull; -import pl.szczodrzynski.edziennik.utils.Colors; - -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.COLOR_MODE_DEFAULT; - -public class GradeDetailsDialog { - private App app; - private Context context; - private int profileId; - - public GradeDetailsDialog(Context context) { - this.context = context; - this.profileId = App.profileId; - } - public GradeDetailsDialog(Context context, int profileId) { - this.context = context; - this.profileId = profileId; - } - - public MaterialDialog dialog; - private DialogGradeDetailsBinding b; - private DialogInterface.OnDismissListener dismissListener; - public boolean callDismissListener = true; - - public GradeDetailsDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) { - this.dismissListener = dismissListener; - return this; - } - public void performDismiss(DialogInterface dialogInterface) { - if (callDismissListener && dismissListener != null) { - dismissListener.onDismiss(dialogInterface); - } - callDismissListener = true; - } - - public void show(App _app, GradeFull grade) - { - this.app = _app; - dialog = new MaterialDialog.Builder(context) - .customView(R.layout.dialog_grade_details, true) - .positiveText(R.string.close) - .autoDismiss(false) - .onPositive((dialog, which) -> dialog.dismiss()) - .dismissListener(this::performDismiss) - .show(); - - View root = dialog.getCustomView(); - assert root != null; - - b = DialogGradeDetailsBinding.bind(root); - - b.setGrade(grade); - - int gradeColor; - if (app.profile.getGradeColorMode() == COLOR_MODE_DEFAULT) { - gradeColor = grade.color; - } - else { - gradeColor = Colors.gradeToColor(grade); - } - - DecimalFormat format = new DecimalFormat("#.##"); - if (grade.weight < 0) { - grade.weight *= -1; - } - if (grade.type == Grade.TYPE_DESCRIPTIVE || grade.type == Grade.TYPE_TEXT || grade.type == Grade.TYPE_BEHAVIOUR) { - b.setWeightText(null); - grade.weight = 0; - } - else { - if (grade.type == Grade.TYPE_POINT) { - b.setWeightText(app.getString(R.string.grades_max_points_format, format.format(grade.valueMax))); - } - else if (grade.weight == 0) { - b.setWeightText(app.getString(R.string.grades_weight_not_counted)); - } - else { - b.setWeightText(app.getString(R.string.grades_weight_format, format.format(grade.weight))); - } - } - - b.setCommentVisible(false); - - b.setDevMode(App.devMode); - - b.gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff); - b.gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)); - - AsyncTask.execute(() -> { - - List historyList = app.db.gradeDao().getAllWithParentIdNow(profileId, grade.id); - - if (historyList.size() == 0) { - b.setHistoryVisible(false); - return; - } - b.setHistoryVisible(true); - b.gradeHistoryNest.post(() -> { - b.gradeHistoryNest.setNestedScrollingEnabled(false); - b.gradeHistoryList.setHasFixedSize(false); - b.gradeHistoryList.setNestedScrollingEnabled(false); - b.gradeHistoryList.setLayoutManager(new LinearLayoutManager(context)); - b.gradeHistoryList.setAdapter(new GradesListAdapter(context, historyList)); - }); - }); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt new file mode 100644 index 00000000..20f3ad21 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt @@ -0,0 +1,92 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.grade + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.GradeFull +import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog +import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext + +class GradeDetailsDialog( + val activity: AppCompatActivity, + val grade: GradeFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "GradeDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: DialogGradeDetailsBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogGradeDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + val manager = app.gradesManager + + val gradeColor = manager.getGradeColor(grade) + b.grade = grade + b.weightText = manager.getWeightString(app, grade) + b.commentVisible = false + b.devMode = App.debugMode + b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt()) + b.gradeName.background.setTintColor(gradeColor) + + b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade) + + b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null + b.customValueLayout.isVisible = b.customValueDivider.isVisible + b.customValueButton.onClick { + GradesConfigDialog(activity, reloadOnDismiss = true) + } + + launch { + val historyList = withContext(Dispatchers.Default) { + app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) + } + if (historyList.isEmpty()) { + b.historyVisible = false + return@launch + } + b.historyVisible = true + //b.gradeHistoryNest.isNestedScrollingEnabled = false + b.gradeHistoryList.adapter = GradesAdapter(activity, { + GradeDetailsDialog(activity, it) + }).also { it.items = historyList.toMutableList() } + b.gradeHistoryList.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt new file mode 100644 index 00000000..265bd17b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-24. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.home + +import android.text.InputType +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.afollestad.materialdialogs.MaterialDialog +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Profile + +class StudentNumberDialog( + val activity: AppCompatActivity, + val profile: Profile, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + private const val TAG = "StudentNumberDialog" + } + + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + MaterialDialog.Builder(activity) + .title(R.string.card_lucky_number_set_title) + .content(R.string.card_lucky_number_set_text) + .inputType(InputType.TYPE_CLASS_NUMBER) + .input(null, if (profile.studentNumber == -1) "" else profile.studentNumber.toString()) { _: MaterialDialog?, input: CharSequence -> + try { + profile.studentNumber = input.toString().toInt() + } catch (e: Exception) { + Toast.makeText(activity, R.string.incorrect_format, Toast.LENGTH_SHORT).show() + } + } + .dismissListener { + onDismissListener?.invoke(TAG) + }.show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt new file mode 100644 index 00000000..b15803ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-19. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.lessonchange + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding +import pl.szczodrzynski.navlib.getColorFromAttr + +class LessonChangeAdapter( + val context: Context, + private val onItemClick: ((lesson: LessonFull) -> Unit)? = null +) : RecyclerView.Adapter() { + + var items = listOf() + + private val arrowRight = " → " + private val bullet = " • " + private val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(context) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = TimetableLessonBinding.inflate(inflater, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val lesson = items[position] + val b = holder.b + + b.root.onClick { + onItemClick?.invoke(lesson) + } + + val startTime = lesson.displayStartTime ?: return + val endTime = lesson.displayEndTime ?: return + + val timeRange = "${startTime.stringHM} - ${endTime.stringHM}".asColoredSpannable(colorSecondary) + + b.unread = false + b.root.background = null + + b.root.updateLayoutParams { + topMargin = 16.dp + } + + // teacher + val teacherInfo = if (lesson.teacherId != null && lesson.teacherId == lesson.oldTeacherId) + lesson.teacherName ?: "?" + else + mutableListOf().apply { + lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) } + lesson.teacherName?.let { add(it) } + }.concat(arrowRight) + + // team + val teamInfo = if (lesson.teamId != null && lesson.teamId == lesson.oldTeamId) + lesson.teamName ?: "?" + else + mutableListOf().apply { + lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) } + lesson.teamName?.let { add(it) } + }.concat(arrowRight) + + // classroom + val classroomInfo = if (lesson.classroom != null && lesson.classroom == lesson.oldClassroom) + lesson.classroom ?: "?" + else + mutableListOf().apply { + lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) } + lesson.classroom?.let { add(it) } + }.concat(arrowRight) + + + b.lessonNumber = lesson.displayLessonNumber + b.subjectName.text = lesson.displaySubjectName?.let { + if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE) + it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) + else + it + } + b.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) + b.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) + + //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) + when (lesson.type) { + Lesson.TYPE_NORMAL -> { + b.annotationVisible = false + } + Lesson.TYPE_CANCELLED -> { + b.annotationVisible = true + b.annotation.setText(R.string.timetable_lesson_cancelled) + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_cancelled_color), + PorterDuff.Mode.SRC_ATOP + ) + //lb.subjectName.typeface = Typeface.DEFAULT + } + Lesson.TYPE_CHANGE -> { + b.annotationVisible = true + when { + lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId + && lesson.oldSubjectName != null && lesson.oldTeacherName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + "${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}" + ) + + lesson.subjectId != lesson.oldSubjectId && lesson.oldSubjectName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + lesson.oldSubjectName ?: "?" + ) + + lesson.teacherId != lesson.oldTeacherId && lesson.oldTeacherName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + lesson.oldTeacherName ?: "?" + ) + else -> b.annotation.setText(R.string.timetable_lesson_change) + } + + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_change_color), + PorterDuff.Mode.SRC_ATOP + ) + } + Lesson.TYPE_SHIFTED_SOURCE -> { + b.annotationVisible = true + when { + lesson.date != lesson.oldDate && lesson.date != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_other_day, + lesson.date?.stringY_m_d ?: "?", + lesson.startTime?.stringHM ?: "" + ) + + lesson.startTime != lesson.oldStartTime && lesson.startTime != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_same_day, + lesson.startTime?.stringHM ?: "?" + ) + + else -> b.annotation.setText(R.string.timetable_lesson_shifted) + } + + b.annotation.background.setTintColor(R.attr.timetable_lesson_shifted_source_color.resolveAttr(context)) + } + Lesson.TYPE_SHIFTED_TARGET -> { + b.annotationVisible = true + when { + lesson.date != lesson.oldDate && lesson.oldDate != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_from_other_day, + lesson.oldDate?.stringY_m_d ?: "?", + lesson.oldStartTime?.stringHM ?: "" + ) + + lesson.startTime != lesson.oldStartTime && lesson.oldStartTime != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_from_same_day, + lesson.oldStartTime?.stringHM ?: "?" + ) + + else -> b.annotation.setText(R.string.timetable_lesson_shifted_from) + } + + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_shifted_target_color), + PorterDuff.Mode.SRC_ATOP + ) + } + } + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: TimetableLessonBinding) : RecyclerView.ViewHolder(b.root) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.java deleted file mode 100644 index 4a298324..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.java +++ /dev/null @@ -1,77 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.dialogs.lessonchange; - -import android.content.Context; -import android.view.View; - -import androidx.databinding.DataBindingUtil; -import androidx.lifecycle.LifecycleOwner; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.afollestad.materialdialogs.MaterialDialog; - -import java.util.ArrayList; -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableAdapter; -import pl.szczodrzynski.edziennik.databinding.DialogLessonChangeListBinding; -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; - -public class LessonChangeDialog { - private App app; - private Context context; - private int profileId; - - public LessonChangeDialog(Context context) { - this.context = context; - this.profileId = App.profileId; - } - public LessonChangeDialog(Context context, int profileId) { - this.context = context; - this.profileId = profileId; - } - - private MaterialDialog dialog; - private DialogLessonChangeListBinding b; - - public void show(App _app, Date date) - { - this.app = _app; - dialog = new MaterialDialog.Builder(context) - .title(date.getFormattedString()) - .customView(R.layout.dialog_lesson_change_list, false) - .positiveText(R.string.close) - .autoDismiss(false) - .onPositive((dialog, which) -> dialog.dismiss()) - .show(); - if (dialog.getCustomView() == null) - return; - b = DataBindingUtil.bind(dialog.getCustomView()); - if (b == null) - return; - - b.lessonChangeView.setHasFixedSize(true); - b.lessonChangeView.setLayoutManager(new LinearLayoutManager(context)); - - app.db.lessonDao().getAllByDate(profileId, date, Time.getNow()).observe((LifecycleOwner) context, lessons -> { - if (app == null || app.profile == null || b == null) - return; - - List changedLessons = new ArrayList<>(); - for (LessonFull lesson: lessons) { - if (lesson.changeId != 0) { - changedLessons.add(lesson); - } - } - - app.db.eventDao().getAllByDate(profileId, date).observe((LifecycleOwner) context, events -> { - TimetableAdapter adapter = new TimetableAdapter(context, date, changedLessons, events == null ? new ArrayList<>() : events); - b.lessonChangeView.setAdapter(adapter); - b.lessonChangeView.setVisibility(View.VISIBLE); - }); - }); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt new file mode 100644 index 00000000..78d40e68 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt @@ -0,0 +1,76 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.lessonchange + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.DialogLessonChangeListBinding +import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class LessonChangeDialog( + val activity: AppCompatActivity, + val profileId: Int, + private val defaultDate: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + const val TAG = "LessonChangeDialog" + } + + private val app by lazy { activity.application as App } + + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var b: DialogLessonChangeListBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + job = Job() + onShowListener?.invoke(TAG) + b = DialogLessonChangeListBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(defaultDate.formattedString) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .create() + loadLessonChanges() + }} + + private fun loadLessonChanges() { launch { + val lessonChanges = withContext(Dispatchers.Default) { + app.db.timetableDao().getChangesForDateNow(profileId, defaultDate) + } + + val adapter = LessonChangeAdapter( + activity, + onItemClick = { + LessonDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ).apply { + items = lessonChanges + } + + b.lessonChangeView.adapter = adapter + b.lessonChangeView.layoutManager = LinearLayoutManager(activity) + + dialog.show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt new file mode 100644 index 00000000..e7d66ed5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import android.annotation.SuppressLint +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AttendanceConfigDialogBinding +import pl.szczodrzynski.edziennik.onChange + +class AttendanceConfigDialog( + val activity: AppCompatActivity, + private val reloadOnDismiss: Boolean = true, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + const val TAG = "GradesConfigDialog" + } + + private val app by lazy { activity.application as App } + private val profileConfig by lazy { app.config.getFor(app.profileId).attendance } + + private lateinit var b: AttendanceConfigDialogBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = AttendanceConfigDialogBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_attendance_config) + .setView(b.root) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + saveConfig() + onDismissListener?.invoke(TAG) + if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget() + } + .create() + initView() + loadConfig() + dialog.show() + }} + + @SuppressLint("SetTextI18n") + private fun loadConfig() { + b.useSymbols.isChecked = profileConfig.useSymbols + b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays + b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth + } + + private fun saveConfig() { + // nothing to do here, yet + } + + private fun initView() { + b.useSymbols.onChange { _, isChecked -> + profileConfig.useSymbols = isChecked + } + b.groupConsecutiveDays.onChange { _, isChecked -> + profileConfig.groupConsecutiveDays = isChecked + } + b.showPresenceInMonth.onChange { _, isChecked -> + profileConfig.showPresenceInMonth = isChecked + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt new file mode 100644 index 00000000..e74f3577 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt @@ -0,0 +1,164 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-16 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import android.annotation.SuppressLint +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import it.sephiroth.android.library.numberpicker.doOnStopTrackingTouch +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.databinding.DialogConfigGradesBinding +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES +import java.util.* + +class GradesConfigDialog( + val activity: AppCompatActivity, + private val reloadOnDismiss: Boolean = true, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + const val TAG = "GradesConfigDialog" + } + + private val app by lazy { activity.application as App } + private val config by lazy { app.config.grades } + private val profileConfig by lazy { app.config.getFor(app.profileId).grades } + + private lateinit var b: DialogConfigGradesBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = DialogConfigGradesBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_grades_config) + .setView(b.root) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + saveConfig() + onDismissListener?.invoke(TAG) + if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget() + } + .create() + initView() + loadConfig() + dialog.show() + }} + + @SuppressLint("SetTextI18n") + private fun loadConfig() { + b.customPlusCheckBox.isChecked = profileConfig.plusValue != null + b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked + b.customMinusCheckBox.isChecked = profileConfig.minusValue != null + b.customMinusValue.isVisible = b.customMinusCheckBox.isChecked + + b.customPlusValue.progress = profileConfig.plusValue ?: 0.5f + b.customMinusValue.progress = profileConfig.minusValue ?: 0.25f + + when (config.orderBy) { + ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio + ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio + else -> null + }?.isChecked = true + + when (profileConfig.colorMode) { + COLOR_MODE_DEFAULT -> b.gradeColorFromERegister + COLOR_MODE_WEIGHTED -> b.gradeColorByValue + else -> null + }?.isChecked = true + + when (profileConfig.yearAverageMode) { + YEAR_ALL_GRADES -> b.gradeAverageMode4 + YEAR_1_AVG_2_AVG -> b.gradeAverageMode0 + YEAR_1_SEM_2_AVG -> b.gradeAverageMode1 + YEAR_1_AVG_2_SEM -> b.gradeAverageMode2 + YEAR_1_SEM_2_SEM -> b.gradeAverageMode3 + else -> null + }?.isChecked = true + + b.dontCountGrades.isChecked = profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty() + b.hideImproved.isChecked = profileConfig.hideImproved + b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight + + if (profileConfig.dontCountGrades.isEmpty()) { + b.dontCountGradesText.setText("nb, 0, bz, bd") + } + else { + b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", ")) + } + } + + private fun saveConfig() { + profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null + profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null + + b.dontCountGradesText.setText( + b.dontCountGradesText + .text + ?.toString() + ?.toLowerCase(Locale.getDefault()) + ?.replace(", ", ",") + ) + profileConfig.dontCountEnabled = b.dontCountGrades.isChecked + profileConfig.dontCountGrades = b.dontCountGradesText.text + ?.split(",") + ?.map { it.trim() } + ?: listOf() + } + + private fun initView() { + b.customPlusCheckBox.onChange { _, isChecked -> + b.customPlusValue.isVisible = isChecked + } + b.customMinusCheckBox.onChange { _, isChecked -> + b.customMinusValue.isVisible = isChecked + } + + // who the hell named those methods + // THIS SHIT DOES NOT EVEN WORK + b.customPlusValue.doOnStopTrackingTouch { + profileConfig.plusValue = it.progress + } + b.customMinusValue.doOnStopTrackingTouch { + profileConfig.minusValue = it.progress + } + + b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC } + b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC } + + b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_DEFAULT } + b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED } + + b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_ALL_GRADES } + b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG } + b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG } + b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM } + b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM } + + b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked } + b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked } + + b.averageWithoutWeightHelp.onClick { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.grades_config_average_without_weight) + .setMessage(R.string.grades_config_average_without_weight_message) + .setPositiveButton(R.string.ok, null) + .show() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt index fa626091..14321317 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt @@ -12,8 +12,6 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding -import pl.szczodrzynski.edziennik.utils.models.Notification -import java.util.* import kotlin.coroutines.CoroutineContext class ProfileRemoveDialog( @@ -52,26 +50,29 @@ class ProfileRemoveDialog( val profileObject = app.db.profileDao().getByIdNow(profileId) ?: return@async app.db.announcementDao().clear(profileId) app.db.attendanceDao().clear(profileId) - app.db.eventDao().clear(profileId) - app.db.eventTypeDao().clear(profileId) - app.db.gradeDao().clear(profileId) - app.db.gradeCategoryDao().clear(profileId) - app.db.lessonDao().clear(profileId) - app.db.lessonChangeDao().clear(profileId) - app.db.luckyNumberDao().clear(profileId) - app.db.noticeDao().clear(profileId) - app.db.subjectDao().clear(profileId) - app.db.teacherDao().clear(profileId) - app.db.teamDao().clear(profileId) - app.db.messageRecipientDao().clear(profileId) - app.db.messageDao().clear(profileId) - app.db.endpointTimerDao().clear(profileId) app.db.attendanceTypeDao().clear(profileId) app.db.classroomDao().clear(profileId) + app.db.configDao().clear(profileId) + app.db.endpointTimerDao().clear(profileId) + app.db.eventDao().clear(profileId) + app.db.eventTypeDao().clear(profileId) + app.db.gradeCategoryDao().clear(profileId) + app.db.gradeDao().clear(profileId) app.db.lessonRangeDao().clear(profileId) + app.db.librusLessonDao().clear(profileId) + app.db.luckyNumberDao().clear(profileId) + app.db.messageDao().clear(profileId) + app.db.messageRecipientDao().clear(profileId) + app.db.noticeDao().clear(profileId) app.db.noticeTypeDao().clear(profileId) + app.db.noticeTypeDao().clear(profileId) + app.db.notificationDao().clear(profileId) + app.db.subjectDao().clear(profileId) + app.db.teacherAbsenceDao().clear(profileId) app.db.teacherAbsenceDao().clear(profileId) app.db.teacherAbsenceTypeDao().clear(profileId) + app.db.teacherDao().clear(profileId) + app.db.teamDao().clear(profileId) app.db.timetableDao().clear(profileId) val loginStoreId = profileObject.loginStoreId @@ -82,22 +83,13 @@ class ProfileRemoveDialog( app.db.profileDao().remove(profileId) app.db.metadataDao().deleteAll(profileId) - val toRemove = ArrayList() - for (notification in app.appConfig.notifications) { - if (notification.profileId == profileId) { - toRemove.add(notification) - } + if (App.profileId == profileId) { + app.profileLoadLast { } } - app.appConfig.notifications.removeAll(toRemove) - - app.profile = null - App.profileId = -1 - - app.profileLoadById(app.profileLastId()) } deferred.await() dialog.dismiss() activity.reloadTarget() Toast.makeText(activity, R.string.dialog_profile_remove_success, Toast.LENGTH_LONG).show() }} -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt new file mode 100644 index 00000000..ad31c02d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.onClick +import kotlin.coroutines.CoroutineContext + +// TODO refactor dialog to allow configuring other profiles +// than the selected one in UI +class NotificationFilterDialog( + val activity: AppCompatActivity, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "NotificationFilterDialog" + private val notificationTypes = listOf( + Notification.TYPE_TIMETABLE_LESSON_CHANGE to R.string.notification_type_timetable_lesson_change, + Notification.TYPE_NEW_GRADE to R.string.notification_type_new_grade, + Notification.TYPE_NEW_EVENT to R.string.notification_type_new_event, + Notification.TYPE_NEW_HOMEWORK to R.string.notification_type_new_homework, + Notification.TYPE_NEW_MESSAGE to R.string.notification_type_new_message, + Notification.TYPE_LUCKY_NUMBER to R.string.notification_type_lucky_number, + Notification.TYPE_NEW_NOTICE to R.string.notification_type_notice, + Notification.TYPE_NEW_ATTENDANCE to R.string.notification_type_attendance, + Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement, + Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event, + Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework, + Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event, + Notification.TYPE_TEACHER_ABSENCE to R.string.notification_type_new_teacher_absence + ) + } + + private lateinit var app: App + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val notificationFilter = mutableListOf() + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + + notificationFilter.clear() + notificationFilter += app.config.forProfile().sync.notificationFilter + val items = notificationTypes.map { app.getString(it.second) }.toTypedArray() + val checkedItems = notificationTypes.map { !notificationFilter.contains(it.first) }.toBooleanArray() + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_notification_filter_title) + //.setMessage(R.string.dialog_notification_filter_text) + .setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> + val type = notificationTypes[which].first + notificationFilter.remove(type) + if (!isChecked) + notificationFilter += type + } + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { + if (notificationFilter.isEmpty()) { + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + return@onClick + } + // warn user when he tries to disable some notifications + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(R.string.notification_filter_warning) + .setPositiveButton(R.string.ok) { _, _ -> + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt new file mode 100644 index 00000000..7fc0caf3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-15. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +import android.text.Html +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.task.AppSync +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import kotlin.coroutines.CoroutineContext + +class RegistrationEnableDialog( + val activity: AppCompatActivity, + val profileId: Int +) : CoroutineScope { + companion object { + private const val TAG = "RegistrationEnableDialog" + } + + private lateinit var app: App + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + app = activity.applicationContext as App + }} + + fun showEventShareDialog(onSuccess: (profile: Profile?) -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.event_manual_need_registration_title) + .setMessage(R.string.event_manual_need_registration_text) + .setPositiveButton(R.string.ok) { dialog, which -> + enableRegistration(onSuccess) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + fun showEnableDialog(onSuccess: (profile: Profile?) -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.registration_enable_dialog_title) + .setMessage(Html.fromHtml(app.getString(R.string.registration_enable_dialog_text))) + .setPositiveButton(R.string.ok) { dialog, which -> + enableRegistration(onSuccess) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun enableRegistration(onSuccess: (profile: Profile?) -> Unit) { launch { + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.registration_enable_progress_text) + .setCancelable(false) + .show() + + val profile = withContext(Dispatchers.Default) { + val profile = app.db.profileDao().getByIdNow(profileId) ?: return@withContext null + profile.registration = Profile.REGISTRATION_ENABLED + + // force full registration of the user + App.config.getFor(profile.id).hash = "" + + AppSync(app, mutableListOf(), listOf(profile), SzkolnyApi(app)).run(0L, markAsSeen = true) + app.db.profileDao().add(profile) + if (profile.id == App.profileId) { + App.profile.registration = profile.registration + } + return@withContext profile + } + + progressDialog?.dismiss() + onSuccess(profile) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt index e8942c1e..b9cbd0a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import kotlin.coroutines.CoroutineContext @@ -94,15 +94,23 @@ class SyncViewListDialog( listOfNotNull(*it.toTypedArray()) } + if (selectedViewIds.isNotEmpty()) { + activity.swipeRefreshLayout.isRefreshing = true + EdziennikTask.syncProfile( + App.profileId, + selectedViewIds + ).enqueue(activity) + } + } + .setNeutralButton(R.string.sync_feature_all) { _, _ -> + dialog.dismiss() + activity.swipeRefreshLayout.isRefreshing = true - EdziennikTask.syncProfile( - App.profileId, - selectedViewIds - ).enqueue(activity) + EdziennikTask.syncProfile(App.profileId).enqueue(activity) } .setNegativeButton(R.string.cancel) { _, _ -> dialog.dismiss() } .show() }} -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt index b8e165d1..443d4734 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt @@ -1,14 +1,13 @@ package pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence import android.content.Context -import android.opengl.Visibility import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull +import pl.szczodrzynski.edziennik.data.db.full.TeacherAbsenceFull import pl.szczodrzynski.edziennik.utils.models.Date class TeacherAbsenceAdapter( @@ -30,7 +29,7 @@ class TeacherAbsenceAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val teacherAbsence: TeacherAbsenceFull = teacherAbsenceList[position] - holder.teacherAbsenceTeacher.text = teacherAbsence.teacherFullName + holder.teacherAbsenceTeacher.text = teacherAbsence.teacherName val time = when (teacherAbsence.timeFrom != null && teacherAbsence.timeTo != null) { true -> when (teacherAbsence.dateFrom.compareTo(teacherAbsence.dateTo)) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt index 55bf8ae0..7ffab515 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt @@ -1,42 +1,57 @@ package pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence -import android.content.Context import android.view.View -import androidx.databinding.DataBindingUtil +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager -import com.afollestad.materialdialogs.MaterialDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.DialogTeacherAbsenceListBinding import pl.szczodrzynski.edziennik.utils.models.Date -class TeacherAbsenceDialog(val context: Context) { +class TeacherAbsenceDialog( + val activity: AppCompatActivity, + val profileId: Int, + val date: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + private const val TAG = "TeacherAbsenceDialog" + } + + private val app by lazy { activity.application as App } - val profileId: Int = App.profileId private lateinit var b: DialogTeacherAbsenceListBinding + private lateinit var dialog: AlertDialog - fun show(app: App, date: Date) { - val dialog = MaterialDialog.Builder(context) - .title(date.formattedString) - .customView(R.layout.dialog_teacher_absence_list, false) - .positiveText(R.string.close) - .autoDismiss(false) - .onPositive { dialog, _ -> dialog.dismiss()} - .show() + init { run { + if (activity.isFinishing) + return@run - val customView: View = dialog.customView ?: return - b = DataBindingUtil.bind(customView) ?: return + b = DialogTeacherAbsenceListBinding.inflate(activity.layoutInflater) + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(date.formattedString) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .create() b.teacherAbsenceView.setHasFixedSize(true) - b.teacherAbsenceView.layoutManager = LinearLayoutManager(context) + b.teacherAbsenceView.layoutManager = LinearLayoutManager(activity) - app.db.teacherAbsenceDao().getAllByDateFull(profileId, date).observe(context as LifecycleOwner, Observer { absenceList -> - val adapter = TeacherAbsenceAdapter(context, date, absenceList) + app.db.teacherAbsenceDao().getAllByDate(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> + val adapter = TeacherAbsenceAdapter(activity, date, absenceList) b.teacherAbsenceView.adapter = adapter b.teacherAbsenceView.visibility = View.VISIBLE }) - } + dialog.show() + }} } 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 new file mode 100644 index 00000000..aefd16f9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt @@ -0,0 +1,418 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-5 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.timetable + +import android.content.Intent +import android.graphics.* +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.util.Log +import android.view.View +import android.view.View.MeasureSpec +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.cardview.widget.CardView +import androidx.core.content.FileProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.wdullaer.materialdatetimepicker.date.DatePickerDialog +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogGenerateBlockTimetableBinding +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import java.io.File +import java.io.FileOutputStream +import kotlin.coroutines.CoroutineContext +import kotlin.math.roundToInt + +class GenerateBlockTimetableDialog( + val activity: AppCompatActivity, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + const val TAG = "GenerateBlockTimetableDialog" + + private const val WIDTH_CONSTANT = 70 + private const val WIDTH_WEEKDAY = 285 + private const val WIDTH_SPACING = 15 + private const val HEIGHT_CONSTANT = 60 + private const val HEIGHT_MINUTE = 3 + private const val HEIGHT_FOOTER = 40 + } + + private val heightProfileName by lazy { if (showProfileName) 100 else 0 } + + private val app by lazy { activity.application as App } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var dialog: AlertDialog + private lateinit var b: DialogGenerateBlockTimetableBinding + + private var showProfileName: Boolean = false + private var showTeachersNames: Boolean = true + private var noColors: Boolean = false + + private var enqueuedWeekDialog: AlertDialog? = null + private var enqueuedWeekStart = Date.getToday() + private var enqueuedWeekEnd = Date.getToday() + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + EventBus.getDefault().register(this) + + val weekCurrentStart = Week.getWeekStart() + val weekCurrentEnd = Week.getWeekEnd() + val weekNextStart = weekCurrentEnd.clone().stepForward(0, 0, 1) + val weekNextEnd = weekNextStart.clone().stepForward(0, 0, 6) + + b = DialogGenerateBlockTimetableBinding.inflate(activity.layoutInflater) + + b.withChangesCurrentWeekRadio.setText(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort) + b.withChangesNextWeekRadio.setText(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekCurrentEnd.formattedStringShort) + + b.showProfileNameCheckbox.setOnCheckedChangeListener { _, isChecked -> showProfileName = isChecked } + b.showTeachersNamesCheckbox.setOnCheckedChangeListener { _, isChecked -> showTeachersNames = isChecked } + b.noColorsCheckbox.setOnCheckedChangeListener { _, isChecked -> noColors = isChecked } + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_range) + .setView(b.root) + .setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.save, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + EventBus.getDefault().unregister(this@GenerateBlockTimetableDialog) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { + when (b.weekSelectionRadioGroup.checkedRadioButtonId) { + R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd) + R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd) + R.id.forSelectedWeekRadio -> selectDate() + } + } + }} + + private fun selectDate() { + val date = Date.getToday() + DatePickerDialog + .newInstance({ _, year, monthOfYear, dayOfMonth -> + val dateSelected = Date(year, monthOfYear, dayOfMonth) + generateBlockTimetable(dateSelected.weekStart, dateSelected.weekEnd) + }, date.year, date.month, date.day) + .apply { + accentColor = R.attr.colorPrimary.resolveAttr(this@GenerateBlockTimetableDialog.activity) + show(this@GenerateBlockTimetableDialog.activity.supportFragmentManager, "DatePickerDialog") + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + if (event.profileId == App.profileId) { + enqueuedWeekDialog?.dismiss() + generateBlockTimetable(enqueuedWeekStart, enqueuedWeekEnd) + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + enqueuedWeekDialog?.dismiss() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + dialog.dismiss() + enqueuedWeekDialog?.dismiss() + } + + private fun generateBlockTimetable(weekStart: Date, weekEnd: Date) { launch { + val weekDays = mutableListOf>() + for (i in weekStart.weekDay..weekEnd.weekDay) { + weekDays.add(mutableListOf()) + } + + val allLessons = withContext(Dispatchers.Default) { + app.db.timetableDao().getBetweenDatesNow(weekStart, weekEnd) + } + val lessonRanges = mutableMapOf() + + var maxWeekDay = 5 + var minTime: Time? = null + var maxTime: Time? = null + + val lessons: List = allLessons.mapNotNull { lesson -> + if (lesson.profileId != app.profile.id || lesson.type == Lesson.TYPE_NO_LESSONS + || lesson.displayDate == null || lesson.displayStartTime == null || lesson.displayEndTime == null) + return@mapNotNull null + + if (lesson.displayDate!!.weekDay > maxWeekDay) + maxWeekDay = lesson.displayDate!!.weekDay + + lessonRanges[lesson.displayStartTime!!.value] = lesson.displayEndTime!!.value + weekDays[lesson.displayDate!!.weekDay].add(lesson) + + if (minTime == null || lesson.displayStartTime!! < minTime!!) { + minTime = lesson.displayStartTime!!.clone() + } + + if (maxTime == null || lesson.displayEndTime!! > maxTime!!) { + maxTime = lesson.displayEndTime!!.clone() + } + + return@mapNotNull lesson + } + + if (lessons.isEmpty()) { + if (enqueuedWeekDialog != null) { + return@launch + } + enqueuedWeekDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.timetable_syncing_text) + .setCancelable(false) + .show() + + enqueuedWeekStart = weekStart + enqueuedWeekEnd = weekEnd + + EdziennikTask.syncProfile( + profileId = App.profileId, + viewIds = listOf( + MainActivity.DRAWER_ITEM_TIMETABLE to 0 + ), + arguments = JsonObject( + "weekStart" to weekStart.stringY_m_d + ) + ).enqueue(activity) + return@launch + } + + val progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_progress_title) + .setMessage(R.string.timetable_generate_progress_text) + .show() + + if (minTime == null) { + progressDialog.dismiss() + // TODO: Toast + return@launch + } + + dialog.dismiss() + + val uri = withContext(Dispatchers.Default) { + + val diff = Time.diff(maxTime, minTime) + + val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING + val imageHeight = heightProfileName + HEIGHT_CONSTANT + diff.inMinutes * HEIGHT_MINUTE + HEIGHT_FOOTER + val bitmap = Bitmap.createBitmap(imageWidth + 20, imageHeight + 30, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + + if (noColors) canvas.drawARGB(255, 255, 255, 255) + else canvas.drawARGB(255, 225, 225, 225) + + val paint = Paint().apply { + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + lessons.forEach { lesson -> + val lessonLength = Time.diff(lesson.displayEndTime, lesson.displayStartTime) + val firstOffset = Time.diff(lesson.displayStartTime, minTime) + val lessonWeekDay = lesson.displayDate!!.weekDay + + val left = WIDTH_CONSTANT + lessonWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) + val top = heightProfileName + HEIGHT_CONSTANT + firstOffset.inMinutes * HEIGHT_MINUTE + + val blockWidth = WIDTH_WEEKDAY + val blockHeight = lessonLength.inMinutes * HEIGHT_MINUTE + + val viewWidth = 380.dp + val viewHeight = lessonLength.inMinutes * 4.dp + + val layout = activity.layoutInflater.inflate(R.layout.row_timetable_block_item, null) as LinearLayout + + val item: LinearLayout = layout.findViewById(R.id.timetableItemLayout) + val card: CardView = layout.findViewById(R.id.timetableItemCard) + val subjectName: TextView = layout.findViewById(R.id.timetableItemSubjectName) + val classroomName: TextView = layout.findViewById(R.id.timetableItemClassroomName) + val teacherName: TextView = layout.findViewById(R.id.timetableItemTeacherName) + val teamName: TextView = layout.findViewById(R.id.timetableItemTeamName) + + if (noColors) { + card.setCardBackgroundColor(Color.WHITE) + card.cardElevation = 0f + item.setBackgroundResource(R.drawable.bg_rounded_16dp_outline) + subjectName.setTextColor(Color.BLACK) + classroomName.setTextColor(0xffaaaaaa.toInt()) + teacherName.setTextColor(0xffaaaaaa.toInt()) + teamName.setTextColor(0xffaaaaaa.toInt()) + } + + subjectName.text = lesson.displaySubjectName ?: "" + classroomName.text = lesson.displayClassroom ?: "" + teacherName.text = lesson.displayTeacherName ?: "" + teamName.text = lesson.displayTeamName ?: "" + + if (!showTeachersNames) teacherName.visibility = View.GONE + + when (lesson.type) { + Lesson.TYPE_NORMAL -> { + } + Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> { + card.setCardBackgroundColor(Color.BLACK) + subjectName.setTextColor(Color.WHITE) + subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable() + ?: "" + } + else -> { + card.setCardBackgroundColor(0xff234158.toInt()) + subjectName.setTextColor(Color.WHITE) + subjectName.setTypeface(null, Typeface.BOLD_ITALIC) + } + } + + layout.isDrawingCacheEnabled = true + layout.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY)) + layout.layout(0, 0, layout.measuredWidth, layout.measuredHeight) + layout.buildDrawingCache(true) + + val itemBitmap = layout.drawingCache + canvas.drawBitmap(itemBitmap, null, Rect(left, top, left + blockWidth, top + blockHeight), paint) + } + + val textPaint = Paint().apply { + setARGB(255, 0, 0, 0) + textAlign = Paint.Align.CENTER + textSize = 30f + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + for (w in 0..maxWeekDay) { + val x = WIDTH_CONSTANT + w * WIDTH_WEEKDAY + w * WIDTH_SPACING + canvas.drawText(Week.getFullDayName(w), x + (WIDTH_WEEKDAY / 2f), heightProfileName + HEIGHT_CONSTANT / 2 + 10f, textPaint) + } + + if (showProfileName) { + textPaint.textSize = 50f + canvas.drawText("${app.profile.name} - plan lekcji, ${weekStart.formattedStringShort} - ${weekEnd.formattedStringShort}", (imageWidth + 20) / 2f, 80f, textPaint) + } + + textPaint.apply { + setARGB(128, 0, 0, 0) + textAlign = Paint.Align.RIGHT + textSize = 26f + typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC) + } + + val footerTextPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt() + canvas.drawText("Wygenerowano w aplikacji Szkolny.eu", imageWidth - 10f, imageHeight - footerTextPaintCenter - 10f, textPaint) + + textPaint.apply { + setARGB(255, 127, 127, 127) + textAlign = Paint.Align.CENTER + textSize = 16f + typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) + } + + val textPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt() + + val linePaint = Paint().apply { + setARGB(255, 100, 100, 100) + style = Paint.Style.STROKE + pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f) + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + val minTimeInt = ((minTime!!.value / 10000) * 60) + ((minTime!!.value / 100) % 100) + + lessonRanges.forEach { (startTime, endTime) -> + listOf(startTime, endTime).forEach { value -> + val hour = value / 10000 + val minute = (value / 100) % 100 + val time = Time(hour, minute, 0) + + val firstOffset = time.inMinutes - minTimeInt // offset in minutes + val top = (heightProfileName + HEIGHT_CONSTANT + firstOffset * HEIGHT_MINUTE).toFloat() + + canvas.drawText(time.stringHM, WIDTH_CONSTANT / 2f, top - textPaintCenter, textPaint) + canvas.drawLine(WIDTH_CONSTANT.toFloat(), top, imageWidth.toFloat(), top, linePaint) + } + } + + val today = Date.getToday().stringY_m_d + val now = Time.getNow().stringH_M_S + + val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() } + val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png") + + try { + val fos = FileOutputStream(outputFile) + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) + fos.close() + } 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() + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_success_title) + .setMessage(R.string.timetable_generate_success_text) + .setPositiveButton(R.string.share) { dialog, _ -> + dialog.dismiss() + + val intent = Intent(Intent.ACTION_SEND) + intent.setDataAndType(null, "image/*") + intent.putExtra(Intent.EXTRA_STREAM, uri) + activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_intent))) + } + .setNegativeButton(R.string.open) { dialog, _ -> + dialog.dismiss() + + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, "image/*") + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + activity.startActivity(intent) + } + .setNeutralButton(R.string.do_nothing) { dialog, _ -> dialog.dismiss() } + .show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt index fb52df03..6dc6edb6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -8,79 +8,90 @@ import android.content.Intent import android.view.View import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson -import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding +import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.setText -import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualV2Dialog -import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.coroutines.CoroutineContext class LessonDetailsDialog( val activity: AppCompatActivity, val lesson: LessonFull, val onShowListener: ((tag: String) -> Unit)? = null, val onDismissListener: ((tag: String) -> Unit)? = null -) { +) : CoroutineScope { companion object { private const val TAG = "LessonDetailsDialog" } + private lateinit var app: App private lateinit var b: DialogLessonDetailsBinding private lateinit var dialog: AlertDialog + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var adapter: EventListAdapter + private val manager by lazy { app.timetableManager } + init { run { if (activity.isFinishing) return@run onShowListener?.invoke(TAG) + app = activity.applicationContext as App b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) dialog = MaterialAlertDialogBuilder(activity) .setView(b.root) .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } - .setNeutralButton(R.string.add) { dialog, _ -> - EventManualV2Dialog( - activity, - lesson.profileId, - lesson, - onShowListener = onShowListener, - onDismissListener = onDismissListener - ) - /*MaterialAlertDialogBuilder(activity) - .setItems(R.array.main_menu_add_options) { dialog2, which -> - dialog2.dismiss() - EventManualDialog(activity, lesson.profileId) - .show( - activity.application as App, - null, - lesson.displayDate, - lesson.displayStartTime, - when (which) { - 1 -> EventManualDialog.DIALOG_HOMEWORK - else -> EventManualDialog.DIALOG_EVENT - } - ) - - } - .setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() } - .show()*/ - } + .setNeutralButton(R.string.add, null) .setOnDismissListener { onDismissListener?.invoke(TAG) } .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + EventManualDialog( + activity, + lesson.profileId, + defaultLesson = lesson, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + + if (App.debugMode) + b.lessonId.visibility = View.VISIBLE + update() }} private fun update() { b.lesson = lesson val lessonDate = lesson.displayDate ?: return + val lessonTime = lesson.displayStartTime ?: return b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString + b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation) + if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) { b.shiftedLayout.visibility = View.VISIBLE var otherLessonDate: Date? = null @@ -120,7 +131,7 @@ class LessonDetailsDialog( dialog.dismiss() val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply { - putExtra("date", dateStr) + putExtra("timetableDate", dateStr) } activity.sendBroadcast(intent) } @@ -132,29 +143,78 @@ class LessonDetailsDialog( if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) { b.oldSubjectName = lesson.oldSubjectName } - if (lesson.type != Lesson.TYPE_CANCELLED && lesson.subjectId != null) { + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) { b.subjectName = lesson.subjectName } if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) { b.oldTeacherName = lesson.oldTeacherName } - if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teacherId != null) { + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) { b.teacherName = lesson.teacherName } if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) { b.oldClassroom = lesson.oldClassroom } - if (lesson.type != Lesson.TYPE_CANCELLED && lesson.classroom != null) { + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) { b.classroom = lesson.classroom } if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) { b.oldTeamName = lesson.oldTeamName } - if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) { + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) { b.teamName = lesson.teamName } + + adapter = EventListAdapter( + activity, + showWeekDay = false, + showDate = false, + showType = true, + showTime = true, + showSubject = true, + markAsSeen = true, + onItemClick = { + EventDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + }, + onEventEditClick = { + EventManualDialog( + activity, + it.profileId, + editingEvent = it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ) + + app.db.eventDao().getAllByDateTime(lesson.profileId, lessonDate, lessonTime).observe(activity, Observer { events -> + adapter.items = events + if (b.eventsView.adapter == null) { + b.eventsView.adapter = adapter + b.eventsView.apply { + isNestedScrollingEnabled = false + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + adapter.notifyDataSetChanged() + + if (events != null && events.isNotEmpty()) { + b.eventsView.visibility = View.VISIBLE + b.eventsNoData.visibility = View.GONE + } else { + b.eventsView.visibility = View.GONE + b.eventsNoData.visibility = View.VISIBLE + } + }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.java deleted file mode 100644 index 00252858..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.java +++ /dev/null @@ -1,421 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.agenda; - -import android.app.Activity; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.databinding.DataBindingUtil; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.applandeo.materialcalendarview.CalendarView; -import com.applandeo.materialcalendarview.EventDay; -import com.github.tibolte.agendacalendarview.AgendaCalendarView; -import com.github.tibolte.agendacalendarview.CalendarPickerController; -import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent; -import com.github.tibolte.agendacalendarview.models.CalendarEvent; -import com.github.tibolte.agendacalendarview.models.IDayItem; -import com.mikepenz.iconics.IconicsColor; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.IconicsSize; -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.MainActivity; -import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull; -import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding; -import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding; -import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; -import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; -import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListDialog; -import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog; -import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog; -import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog; -import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter; -import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent; -import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer; -import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter; -import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent; -import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; -import pl.szczodrzynski.edziennik.utils.Colors; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; -import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem; -import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem; - -import static pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.TYPE_EVENT; -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.AGENDA_CALENDAR; -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.AGENDA_DEFAULT; -import static pl.szczodrzynski.edziennik.utils.Utils.intToStr; - -public class AgendaFragment extends Fragment { - - private App app = null; - private MainActivity activity = null; - private FragmentAgendaDefaultBinding b_default = null; - private FragmentAgendaCalendarBinding b_calendar = null; - private int viewType = AGENDA_DEFAULT; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - activity = (MainActivity) getActivity(); - if (getActivity() == null || getContext() == null) - return null; - app = (App) activity.getApplication(); - getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true); - if (app.profile == null) - return inflater.inflate(R.layout.fragment_loading, container, false); - // activity, context and profile is valid - viewType = app.profile.getAgendaViewType(); - if (viewType == AGENDA_DEFAULT) { - b_default = DataBindingUtil.inflate(inflater, R.layout.fragment_agenda_default, container, false); - return b_default.getRoot(); - } - else { - b_calendar = DataBindingUtil.inflate(inflater, R.layout.fragment_agenda_calendar, container, false); - return b_calendar.getRoot(); - } - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - if (app == null || app.profile == null || activity == null || (b_default == null && b_calendar == null) || !isAdded()) - return; - - activity.getBottomSheet().prependItems( - new BottomSheetPrimaryItem(true) - .withTitle(R.string.menu_add_event) - .withDescription(R.string.menu_add_event_desc) - .withIcon(CommunityMaterial.Icon.cmd_calendar_plus) - .withOnClickListener(v3 -> { - activity.getBottomSheet().close(); - new MaterialDialog.Builder(activity) - .title(R.string.main_menu_add) - .items(R.array.main_menu_add_options) - .itemsCallback((dialog, itemView, position, text) -> { - switch (position) { - case 0: - new EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_EVENT); - break; - case 1: - new EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_HOMEWORK); - break; - } - }) - .show(); - }), - new BottomSheetPrimaryItem(true) - .withTitle(R.string.menu_agenda_change_view) - .withIcon(viewType == AGENDA_DEFAULT ? CommunityMaterial.Icon.cmd_calendar : CommunityMaterial.Icon2.cmd_view_list) - .withOnClickListener(v3 -> { - activity.getBottomSheet().close(); - viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT; - app.profile.setAgendaViewType(viewType); - app.profileSaveAsync(); - activity.reloadTarget(); - }), - new BottomSheetSeparatorItem(true), - new BottomSheetPrimaryItem(true) - .withTitle(R.string.menu_mark_as_read) - .withIcon(CommunityMaterial.Icon.cmd_eye_check) - .withOnClickListener(v3 -> { - activity.getBottomSheet().close(); - AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_EVENT, true)); - Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); - }) - ); - activity.gainAttention(); - - if (viewType == AGENDA_DEFAULT) { - createDefaultAgendaView(); - } - else { - createCalendarAgendaView(); - } - } - - private void createDefaultAgendaView() { - List unreadEventDates = new ArrayList<>(); - - final Handler handler = new Handler(); - handler.postDelayed(() -> AsyncTask.execute(() -> { - if (app == null || app.profile == null || activity == null || b_default == null || !isAdded()) - return; - - List eventList = new ArrayList<>(); - - List lessonChangeCounters = app.db.lessonChangeDao().getLessonChangeCountersNow(App.profileId); - for (LessonChangeCounter counter : lessonChangeCounters) { - Calendar startTime = Calendar.getInstance(); - Calendar endTime = Calendar.getInstance(); - if (counter.lessonChangeDate == null) { - continue; - } - startTime.set(counter.lessonChangeDate.year, counter.lessonChangeDate.month - 1, counter.lessonChangeDate.day, 10, 0, 0); - endTime.setTimeInMillis(startTime.getTimeInMillis() + (1000 * 60 * 45)); - eventList.add(new LessonChangeEvent( - counter.lessonChangeDate.getInMillis(), - 0xff78909c, - Colors.legibleTextColor(0xff78909c), - startTime, - endTime, - counter.profileId, - counter.lessonChangeDate, - counter.lessonChangeCount - )); - } - - if (app.profile.getStudentData("showTeacherAbsences", true)) { - List teacherAbsenceList = app.db.teacherAbsenceDao().getAllFullNow(App.profileId); - List teacherAbsenceCounters = new ArrayList<>(); - - for (TeacherAbsenceFull absence : teacherAbsenceList) { - for (Date date = absence.getDateFrom().clone(); date.compareTo(absence.getDateTo()) < 1; date.stepForward(0, 0, 1)) { - boolean counterFound = false; - for (TeacherAbsenceCounter counter : teacherAbsenceCounters) { - if (counter.getTeacherAbsenceDate().compareTo(date) == 0) { - counter.setTeacherAbsenceCount(counter.getTeacherAbsenceCount() + 1); - counterFound = true; - break; - } - } - - if (!counterFound) { - teacherAbsenceCounters.add(new TeacherAbsenceCounter(date.clone(), 1)); - } - } - } - - for (TeacherAbsenceCounter counter : teacherAbsenceCounters) { - Calendar startTime = Calendar.getInstance(); - Calendar endTime = Calendar.getInstance(); - Date date = counter.getTeacherAbsenceDate(); - startTime.set(date.year, date.month - 1, date.day, 10, 0, 0); - endTime.setTimeInMillis(startTime.getTimeInMillis() + (1000 * 60 * 45)); - eventList.add(new TeacherAbsenceEvent( - date.getInMillis(), - 0xffff1744, - Colors.legibleTextColor(0xffff1744), - startTime, - endTime, - App.profileId, - date, - counter.getTeacherAbsenceCount() - )); - } - } - - - List events = app.db.eventDao().getAllNow(App.profileId); - for (EventFull event : events) { - Calendar startTime = Calendar.getInstance(); - Calendar endTime = Calendar.getInstance(); - if (event.eventDate == null) - continue; - startTime.set( - event.eventDate.year, - event.eventDate.month - 1, - event.eventDate.day, - event.startTime == null ? 0 : event.startTime.hour, - event.startTime == null ? 0 : event.startTime.minute, - event.startTime == null ? 0 : event.startTime.second - ); - endTime.setTimeInMillis(startTime.getTimeInMillis() + (1000 * 60 * 45)); - eventList.add(new BaseCalendarEvent(event.typeName + " - " + event.topic, - "", - (event.startTime == null ? getString(R.string.agenda_event_all_day) : event.startTime.getStringHM()) + - Utils.bs(", ", event.subjectLongName) + - Utils.bs(", ", event.teacherFullName) + - Utils.bs(", ", event.teamName), - event.getColor(), - Colors.legibleTextColor(event.getColor()), - startTime, - endTime, - event.startTime == null, - event.id, !event.seen)); - if (!event.seen) { - unreadEventDates.add(event.eventDate.getValue()); - } - } - - /*List lessonChanges = app.db.lessonChangeDao().getAllChangesWithLessonsNow(App.profileId); - for (LessonFull lesson: lessonChanges) { - Calendar startTime = Calendar.getInstance(); - Calendar endTime = Calendar.getInstance(); - if (lesson.lessonDate == null) { - continue; - } - startTime.set(lesson.lessonDate.year, lesson.lessonDate.month - 1, lesson.lessonDate.day, lesson.startTime.hour, lesson.startTime.minute, lesson.startTime.second); - endTime.setTimeInMillis(startTime.getTimeInMillis() + (1000 * 60 * 45)); - String description = lesson.changeTypeStr(activity); - if (lesson.changeType != TYPE_CANCELLED) { - if (lesson.subjectId != lesson.changeSubjectId && lesson.teacherId != lesson.changeTeacherId) { - description += " -> " + bs(null, lesson.changeSubjectLongName, ", ") + bs(lesson.changeTeacherFullName); - } else if (lesson.subjectId != lesson.changeSubjectId) { - description += " -> " + bs(lesson.changeSubjectLongName); - } else if (lesson.teacherId != lesson.changeTeacherId) { - description += " -> " + bs(lesson.changeTeacherFullName); - } - } - eventList.add(new BaseCalendarEvent(description, - "", - (lesson.startTime.getStringHM()) + - Utils.bs(", ", lesson.subjectLongName) + - Utils.bs(", ", lesson.teacherFullName) + - Utils.bs(", ", lesson.teamName), - 0xff78909c, - Colors.legibleTextColor(0xff78909c), - startTime, - endTime, - false, - (int)lesson.changeId, false)); - }*/ - - activity.runOnUiThread(() -> { - AgendaCalendarView mAgendaCalendarView = b_default.agendaDefaultView; - // minimum and maximum date of our calendar - // 2 month behind, one year ahead, example: March 2015 <-> May 2015 <-> May 2016 - Calendar minDate = Calendar.getInstance(); - Calendar maxDate = Calendar.getInstance(); - - minDate.add(Calendar.MONTH, -2); - minDate.set(Calendar.DAY_OF_MONTH, 1); - maxDate.add(Calendar.MONTH, 2); - - - mAgendaCalendarView.init(eventList, minDate, maxDate, Locale.getDefault(), new CalendarPickerController() { - @Override - public void onDaySelected(IDayItem dayItem) { - } - - @Override - public void onScrollToDate(Calendar calendar) { - int scrolledDate = Date.fromCalendar(calendar).getValue(); - if (unreadEventDates.contains(scrolledDate)) { - AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true)); - unreadEventDates.remove(unreadEventDates.indexOf(scrolledDate)); - } - } - - @Override - public void onEventSelected(CalendarEvent calendarEvent) { - if (calendarEvent instanceof BaseCalendarEvent) { - if (!calendarEvent.isPlaceholder() && !calendarEvent.isAllDay()) { - new EventListDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()), Time.fromMillis(calendarEvent.getStartTime().getTimeInMillis()), true); - } else { - new EventListDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay())); - } - } else if (calendarEvent instanceof LessonChangeEvent) { - new LessonChangeDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay())); - //Toast.makeText(app, "Clicked "+((LessonChangeEvent) calendarEvent).getLessonChangeDate().getFormattedString(), Toast.LENGTH_SHORT).show(); - } else if (calendarEvent instanceof TeacherAbsenceEvent) { - new TeacherAbsenceDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay())); - } - } - }, new LessonChangeEventRenderer(), new TeacherAbsenceEventRenderer()); - b_default.progressBar.setVisibility(View.GONE); - }); - }), 500); - } - - private void createCalendarAgendaView() { - List unreadEventDates = new ArrayList<>(); - - final Handler handler = new Handler(); - handler.postDelayed(() -> AsyncTask.execute(() -> { - if (app == null || app.profile == null || activity == null || b_calendar == null || !isAdded()) - return; - Context c = getContext(); - Activity a = getActivity(); - assert c != null; - assert a != null; - if (!isAdded()) { - return; - } - - List eventList = new ArrayList<>(); - - List events = app.db.eventDao().getAllNow(App.profileId); - for (EventFull event : events) { - if (event.eventDate == null) - continue; - Calendar startTime = Calendar.getInstance(); - startTime.set( - event.eventDate.year, - event.eventDate.month - 1, - event.eventDate.day, - event.startTime == null ? 0 : event.startTime.hour, - event.startTime == null ? 0 : event.startTime.minute, - event.startTime == null ? 0 : event.startTime.second - ); - Drawable eventIcon = new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle).size(IconicsSize.dp(10)).color(IconicsColor.colorInt(event.getColor())); - eventList.add(new EventDay(startTime, eventIcon)); - if (!event.seen) { - unreadEventDates.add(event.eventDate.getValue()); - } - } - - List lessonChanges = app.db.lessonChangeDao().getAllChangesWithLessonsNow(App.profileId); - - for (LessonFull lesson: lessonChanges) { - Calendar startTime = Calendar.getInstance(); - if (lesson.lessonDate == null) { - continue; - } - startTime.set( - lesson.lessonDate.year, - lesson.lessonDate.month - 1, - lesson.lessonDate.day, - lesson.startTime.hour, - lesson.startTime.minute, - lesson.startTime.second); - Drawable eventIcon = new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle).size(IconicsSize.dp(10)).color(IconicsColor.colorInt(0xff78909c)); - eventList.add(new EventDay(startTime, eventIcon)); - } - - getActivity().runOnUiThread(() -> { - //List eventList = new ArrayList<>(); - - //Collections.sort(eventList, new EventListComparator()); - - CalendarView calendarView = b_calendar.agendaCalendarView; - calendarView.setEvents(eventList); - calendarView.setOnDayClickListener(eventDay -> { - Date dayDate = Date.fromCalendar(eventDay.getCalendar()); - int scrolledDate = dayDate.getValue(); - if (unreadEventDates.contains(scrolledDate)) { - AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true)); - unreadEventDates.remove(unreadEventDates.indexOf(scrolledDate)); - } - - new EventListDialog(getContext()).show(app, dayDate); - }); - b_calendar.progressBar.setVisibility(View.GONE); - }); - }), 300); - } - - public static class EventListComparator implements java.util.Comparator { - @Override - public int compare(CalendarEvent o1, CalendarEvent o2) { - return Long.compare(o1.getStartTime().getTimeInMillis(), o2.getStartTime().getTimeInMillis()); - //return (int)(o1.getStartTime().getTimeInMillis() - o2.getStartTime().getTimeInMillis()); - } - } -} 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 new file mode 100644 index 00000000..ec212cea --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -0,0 +1,305 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-25 + */ + +package pl.szczodrzynski.edziennik.ui.modules.agenda + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.applandeo.materialcalendarview.EventDay +import com.github.tibolte.agendacalendarview.CalendarPickerController +import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent +import com.github.tibolte.agendacalendarview.models.CalendarEvent +import com.github.tibolte.agendacalendarview.models.IDayItem +import com.mikepenz.iconics.IconicsColor +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.IconicsSize +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2 +import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont +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.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding +import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding +import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog +import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer +import pl.szczodrzynski.edziennik.utils.Colors +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import java.util.* +import kotlin.coroutines.CoroutineContext + +class AgendaFragment : Fragment(), CoroutineScope { + + private lateinit var activity: MainActivity + private lateinit var b: ViewDataBinding + + private val app by lazy { activity.app } + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private var type: Int = Profile.AGENDA_DEFAULT + private var actualDate: Date? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + if (getActivity() == null || context == null) return null + activity = getActivity() as MainActivity + context?.theme?.applyStyle(Themes.appTheme, true) + type = app.config.forProfile().ui.agendaViewType + b = when (type) { + Profile.AGENDA_DEFAULT -> FragmentAgendaDefaultBinding.inflate(inflater, container, false) + Profile.AGENDA_CALENDAR -> FragmentAgendaCalendarBinding.inflate(inflater, container, false) + else -> return null + } + return b.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + activity.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_add_event) + .withDescription(R.string.menu_add_event_desc) + .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + EventManualDialog(activity, app.profileId, defaultDate = actualDate) + }), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_agenda_change_view) + .withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon.cmd_format_list_bulleted_square) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT + app.config.forProfile().ui.agendaViewType = type + activity.reloadTarget() + }), + BottomSheetSeparatorItem(true), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_mark_as_read) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .withOnClickListener(View.OnClickListener { launch { + activity.bottomSheet.close() + withContext(Dispatchers.Default) { + App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) + } + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }}) + ) + + activity.navView.bottomBar.fabEnable = true + activity.navView.bottomBar.fabExtendedText = getString(R.string.add) + activity.navView.bottomBar.fabIcon = Icon2.cmd_plus + activity.navView.setFabOnClickListener(View.OnClickListener { + EventManualDialog(activity, app.profileId, defaultDate = actualDate) + }) + + activity.gainAttention() + activity.gainAttentionFAB() + + when (type) { + Profile.AGENDA_DEFAULT -> createDefaultAgendaView() + Profile.AGENDA_CALENDAR -> createCalendarAgendaView() + } + } + + private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch { + if (!isAdded) + return@launch + delay(500) + + val eventList = mutableListOf() + + val minDate = Calendar.getInstance().apply { + add(Calendar.MONTH, -2) + set(Calendar.DAY_OF_MONTH, 1) + } + val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) } + + /** + * LESSON CHANGES + */ + if (!isAdded) + return@launch + + val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) } + val lessonChangeCounters = mutableListOf() + + lessons.forEach { lesson -> + lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let { + it.lessonChangeCount += 1 + } ?: run { + lessonChangeCounters.add(LessonChangeCounter( + lesson.displayDate ?: return@forEach, + 1 + )) + } + } + + lessonChangeCounters.forEach { counter -> + eventList.add(LessonChangeEvent( + counter.lessonChangeDate.inMillis, + 0xff78909c.toInt(), + Colors.legibleTextColor(0xff78909c.toInt()), + counter.startTime, + counter.endTime, + app.profileId, + counter.lessonChangeDate, + counter.lessonChangeCount + )) + } + + /** + * TEACHER ABSENCES + */ + if (!isAdded) + return@launch + + val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) + + if (showTeacherAbsences) { + val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) } + val teacherAbsenceCounters = mutableListOf() + + teacherAbsenceList.forEach { absence -> + val date = absence.dateFrom.clone() + + while (date <= absence.dateTo) { + teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let { + it.teacherAbsenceCount += 1 + } ?: run { + teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1)) + } + + date.stepForward(0, 0, 1) + } + } + + teacherAbsenceCounters.forEach { counter -> + eventList.add(TeacherAbsenceEvent( + counter.teacherAbsenceDate.inMillis, + 0xffff1744.toInt(), + Colors.legibleTextColor(0xffff1744.toInt()), + counter.startTime, + counter.endTime, + app.profileId, + counter.teacherAbsenceDate, + counter.teacherAbsenceCount + )) + } + } + + /** + * EVENTS + */ + if (!isAdded) + return@launch + + val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) } + val unreadEventDates = mutableSetOf() + + events.forEach { event -> + eventList.add(BaseCalendarEvent( + "${event.typeName ?: "wydarzenie"} - ${event.topic}", + "", + (if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) + + (event.subjectLongName?.let { ", $it" } ?: "") + + (event.teacherName?.let { ", $it" } ?: "") + + (event.teamName?.let { ", $it" } ?: ""), + event.eventColor, + Colors.legibleTextColor(event.eventColor), + event.startTimeCalendar, + event.endTimeCalendar, + event.time == null, + event.id, + !event.seen + )) + + if (!event.seen) unreadEventDates.add(event.date.value) + } + + b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController { + override fun onDaySelected(dayItem: IDayItem?) {} + + override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch { + val date = Date.fromCalendar(calendar) + actualDate = date + + // Mark as read scrolled date + if (date.value in unreadEventDates) { + withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) } + unreadEventDates.remove(date.value) + } + }} + + override fun onEventSelected(event: CalendarEvent) { + val date = Date.fromCalendar(event.instanceDay) + + when (event) { + is BaseCalendarEvent -> DayDialog(activity, app.profileId, date) + is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date) + is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date) + } + } + + }, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer()) + + b.progressBar.visibility = View.GONE + }}} + + private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { + delay(300) + + val dayList = mutableListOf() + + val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) } + val unreadEventDates = mutableSetOf() + + events.forEach { event -> + val eventIcon = IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle) + .size(IconicsSize.dp(10)) + .color(IconicsColor.colorInt(event.eventColor)) + + dayList.add(EventDay(event.startTimeCalendar, eventIcon)) + + if (!event.seen) unreadEventDates.add(event.date.value) + } + + b.agendaCalendarView.setEvents(dayList) + b.agendaCalendarView.setOnDayClickListener { day -> this@AgendaFragment.launch { + val date = Date.fromCalendar(day.calendar) + + if (date.value in unreadEventDates) { + withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) } + unreadEventDates.remove(date.value) + } + + DayDialog(activity, app.profileId, date) + }} + + b.progressBar.visibility = View.GONE + }}} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.java deleted file mode 100644 index 6c2cfaf2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.java +++ /dev/null @@ -1,9 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange; - -import pl.szczodrzynski.edziennik.utils.models.Date; - -public class LessonChangeCounter { - public int profileId; - public Date lessonChangeDate; - public int lessonChangeCount; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt new file mode 100644 index 00000000..2b264ede --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt @@ -0,0 +1,19 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange + +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class LessonChangeCounter( + val lessonChangeDate: Date, + var lessonChangeCount: Int +) { + val startTime: Calendar + get() = Calendar.getInstance().apply { + set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0) + } + + val endTime: Calendar + get() = Calendar.getInstance().apply { + timeInMillis = startTime.timeInMillis + (45 * 60 * 1000) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.java deleted file mode 100644 index 2490e1bf..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.java +++ /dev/null @@ -1,28 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange; - -import android.view.View; -import android.widget.TextView; - -import androidx.cardview.widget.CardView; - -import com.github.tibolte.agendacalendarview.render.EventRenderer; - -import pl.szczodrzynski.edziennik.R; - -public class LessonChangeEventRenderer extends EventRenderer { - @Override - public void render(View view, LessonChangeEvent event) { - CardView card = view.findViewById(R.id.lesson_change_card); - TextView changeText = view.findViewById(R.id.lesson_change_text); - TextView changeCount = view.findViewById(R.id.lessonChangeCount); - card.setCardBackgroundColor(event.getColor()); - changeText.setTextColor(event.getTextColor()); - changeCount.setTextColor(event.getTextColor()); - changeCount.setText(String.valueOf(event.getLessonChangeCount())); - } - - @Override - public int getEventLayout() { - return R.layout.agenda_event_lesson_change; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt new file mode 100644 index 00000000..e2306039 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt @@ -0,0 +1,21 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange + +import android.view.View +import android.widget.TextView +import androidx.cardview.widget.CardView +import com.github.tibolte.agendacalendarview.render.EventRenderer +import pl.szczodrzynski.edziennik.R + +class LessonChangeEventRenderer : EventRenderer() { + override fun render(view: View?, event: LessonChangeEvent) { + val card = view?.findViewById(R.id.lesson_change_card) + val changeText = view?.findViewById(R.id.lesson_change_text) + val changeCount = view?.findViewById(R.id.lessonChangeCount) + card?.setCardBackgroundColor(event.color) + changeText?.setTextColor(event.textColor) + changeCount?.setTextColor(event.textColor) + changeCount?.text = event.lessonChangeCount.toString() + } + + override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt index 6017be16..71dea025 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt @@ -1,10 +1,19 @@ package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* class TeacherAbsenceCounter ( val teacherAbsenceDate: Date, var teacherAbsenceCount: Int = 0 -) - +) { + val startTime: Calendar + get() = Calendar.getInstance().apply { + set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0) + } + val endTime: Calendar + get() = Calendar.getInstance().apply { + timeInMillis = startTime.timeInMillis + (45 * 60 * 1000) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt index 3789beb8..b437769b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -17,5 +17,5 @@ class TeacherAbsenceEventRenderer : EventRenderer() { changeCount?.text = event.teacherAbsenceCount.toString() } - override fun getEventLayout(): Int { return R.layout.agenda_event_teacher_absence } + override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java index 1b0d4fda..ee88da07 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java @@ -1,6 +1,7 @@ package pl.szczodrzynski.edziennik.ui.modules.announcements; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Typeface; @@ -8,14 +9,16 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import java.util.List; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + import pl.szczodrzynski.edziennik.R; +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull; import pl.szczodrzynski.edziennik.databinding.RowAnnouncementsItemBinding; -import pl.szczodrzynski.edziennik.data.db.modules.announcements.AnnouncementFull; +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils; public class AnnouncementsAdapter extends RecyclerView.Adapter { @@ -51,16 +54,25 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { + b.announcementsItem.setOnClickListener((v -> { if (onClick != null) { onClick.onClick(v, item); } })); - b.announcementsItemSender.setText(item.teacherFullName); - b.announcementsItemTitle.setText(item.subject); - b.announcementsItemText.setText(item.text); - b.announcementsItemDate.setText(item.startDate.getFormattedString()+(item.endDate == null ? "" : " - "+item.endDate.getFormattedString())); - if (!item.seen) { + b.announcementsItemSender.setText(item.getTeacherName()); + b.announcementsItemTitle.setText(item.getSubject()); + b.announcementsItemText.setText(item.getText()); + + if (item.getEndDate() == null && item.getStartDate() != null) { + b.announcementsItemDate.setText(item.getStartDate().getFormattedString()); + } else if (item.getStartDate() != null) { + b.announcementsItemDate.setText(context.getString(R.string.date_relative_format, item.getStartDate().getFormattedStringShort(), item.getEndDate().getFormattedStringShort())); + } + else { + b.announcementsItemDate.setText(""); + } + + if (!item.getSeen()) { b.announcementsItemTitle.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); b.announcementsItemTitle.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); b.announcementsItemSender.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); @@ -70,6 +82,9 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { activity.getBottomSheet().close(); - AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ANNOUNCEMENT, true)); - Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS) { + EdziennikTask.Companion.announcementsRead(App.Companion.getProfileId()).enqueue(requireContext()); + } else { + AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ANNOUNCEMENT, true)); + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); + } }) ); @@ -77,12 +89,22 @@ public class AnnouncementsFragment extends Fragment { recyclerView = b.announcementsView; recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(linearLayoutManager); + recyclerView.addItemDecoration(new SimpleDividerItemDecoration(view.getContext())); + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (recyclerView.canScrollVertically(-1)) { + b.refreshLayout.setEnabled(false); + } + if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) { + b.refreshLayout.setEnabled(true); + } + } + }); - - - app.db.announcementDao().getAll(App.profileId).observe(this, announcements -> { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> { + if (app == null || activity == null || b == null || !isAdded()) return; if (announcements == null) { @@ -98,20 +120,10 @@ public class AnnouncementsFragment extends Fragment { return; }*/ AnnouncementsAdapter announcementsAdapter = new AnnouncementsAdapter(activity, announcements, (v, announcement) -> { - MaterialDialog dialog = new MaterialDialog.Builder(activity) - .title(announcement.subject) - .customView(R.layout.dialog_announcement, true) - .positiveText(R.string.ok) - .show(); - DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView()); - b.text.setText(announcement.teacherFullName+"\n\n"+ (announcement.startDate != null ? announcement.startDate.getFormattedString() : "-")+" do "+ (announcement.endDate != null ? announcement.endDate.getFormattedString() : "-")+"\n\n" +announcement.text); - if (!announcement.seen) { - announcement.seen = true; - AsyncTask.execute(() -> { - app.db.metadataDao().setSeen(App.profileId, announcement, true); - }); - if (recyclerView.getAdapter() != null) - recyclerView.getAdapter().notifyDataSetChanged(); + if (announcement.getText() == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.getSeen() && app.getNetworkUtils().isOnline())) { + EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext()); + } else { + showAnnouncementDetailsDialog(announcement); } }); @@ -129,4 +141,38 @@ public class AnnouncementsFragment extends Fragment { } }); } + + @Override + public void onStart() { + EventBus.getDefault().register(this); + super.onStart(); + } + + @Override + public void onStop() { + EventBus.getDefault().unregister(this); + super.onStop(); + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + public void onAnnouncementGetEvent(AnnouncementGetEvent event) { + EventBus.getDefault().removeStickyEvent(event); + showAnnouncementDetailsDialog(event.getAnnouncement()); + } + + private void showAnnouncementDetailsDialog(AnnouncementFull announcement) { + MaterialDialog dialog = new MaterialDialog.Builder(activity) + .title(announcement.getSubject()) + .customView(R.layout.dialog_announcement, true) + .positiveText(R.string.ok) + .show(); + DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView()); + b.text.setText(announcement.getTeacherName() +"\n\n"+ (announcement.getStartDate() != null ? announcement.getStartDate().getFormattedString() : "-") + (announcement.getEndDate() != null ? " do " + announcement.getEndDate().getFormattedString() : "")+"\n\n" +announcement.getText()); + if (!announcement.getSeen() && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { + announcement.setSeen(true); + AsyncTask.execute(() -> App.db.metadataDao().setSeen(App.Companion.getProfileId(), announcement, true)); + if (recyclerView.getAdapter() != null) + recyclerView.getAdapter().notifyDataSetChanged(); + } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java deleted file mode 100644 index e1e53f5a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java +++ /dev/null @@ -1,128 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.attendance; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.modules.attendance.AttendanceFull; - -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_RELEASED; - -public class AttendanceAdapter extends RecyclerView.Adapter { - private Context context; - public List attendanceList; - - //getting the context and product list with constructor - public AttendanceAdapter(Context mCtx, List noticeList) { - this.context = mCtx; - this.attendanceList = noticeList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_attendance_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - AttendanceFull attendance = attendanceList.get(position); - - holder.attendanceLessonTopic.setText(attendance.lessonTopic); - holder.attendanceTeacher.setText(attendance.teacherFullName); - holder.attendanceSubject.setText(attendance.subjectLongName); - holder.attendanceDate.setText(attendance.lessonDate.getStringDmy()); - holder.attendanceTime.setText(attendance.startTime.getStringHM()); - - switch (attendance.type) { - case TYPE_ABSENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xfff44336, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent); - break; - case TYPE_ABSENT_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffaeea00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent_excused); - break; - case TYPE_BELATED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffca28, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated); - break; - case TYPE_BELATED_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff4bb733, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated_excused); - break; - case TYPE_RELEASED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff9e9e9e, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_released); - break; - case TYPE_PRESENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffae00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_present); - break; - default: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff03a9f4, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText("?"); - break; - } - - if (!attendance.seen) { - holder.attendanceLessonTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); - holder.attendanceLessonTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - attendance.seen = true; - AsyncTask.execute(() -> { - app.db.metadataDao().setSeen(App.profileId, attendance, true); - //Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure"); - //context.sendBroadcast(i); - }); - } - else { - holder.attendanceLessonTopic.setBackground(null); - } - } - - @Override - public int getItemCount() { - return attendanceList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - TextView attendanceType; - TextView attendanceLessonTopic; - TextView attendanceSubject; - TextView attendanceTeacher; - TextView attendanceDate; - TextView attendanceTime; - - ViewHolder(View itemView) { - super(itemView); - attendanceType = itemView.findViewById(R.id.attendanceType); - attendanceLessonTopic = itemView.findViewById(R.id.attendanceLessonTopic); - attendanceSubject = itemView.findViewById(R.id.attendanceSubject); - attendanceTeacher = itemView.findViewById(R.id.attendanceTeacher); - attendanceDate = itemView.findViewById(R.id.attendanceDate); - attendanceTime = itemView.findViewById(R.id.attendanceTime); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt new file mode 100644 index 00000000..0dcae97e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.animation.ObjectAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.* +import pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder.* +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import kotlin.coroutines.CoroutineContext + +class AttendanceAdapter( + val activity: AppCompatActivity, + val type: Int, + var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "AttendanceAdapter" + private const val ITEM_TYPE_ATTENDANCE = 0 + private const val ITEM_TYPE_DAY_RANGE = 1 + private const val ITEM_TYPE_MONTH = 2 + private const val ITEM_TYPE_SUBJECT = 3 + private const val ITEM_TYPE_TYPE = 4 + private const val ITEM_TYPE_EMPTY = 5 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_ATTENDANCE -> AttendanceViewHolder(inflater, parent) + ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent) + ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent) + ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent) + ITEM_TYPE_TYPE -> TypeViewHolder(inflater, parent) + ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is AttendanceFull -> ITEM_TYPE_ATTENDANCE + is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE + is AttendanceMonth -> ITEM_TYPE_MONTH + is AttendanceSubject -> ITEM_TYPE_SUBJECT + is AttendanceTypeGroup -> ITEM_TYPE_TYPE + is AttendanceEmpty -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is AttendanceFull) { + onAttendanceClick?.invoke(model) + return@OnClickListener + } + if (model !is ExpandableItemModel<*>) + return@OnClickListener + expandModel(model, view) + } + + fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) { + model ?: return + val position = items.indexOf(model) + if (position == -1) + return + + view?.findViewById(R.id.dropdownIcon)?.let { dropdownIcon -> + ObjectAnimator.ofFloat( + dropdownIcon, + View.ROTATION, + if (model.state == STATE_CLOSED) 0f else 180f, + if (model.state == STATE_CLOSED) 180f else 0f + ).setDuration(200).start(); + } + + if (model is AttendanceDayRange || model is AttendanceMonth || model is AttendanceTypeGroup) { + // hide the preview, show summary + val preview = view?.findViewById(R.id.previewContainer) + val summary = view?.findViewById(R.id.summaryContainer) + val percentage = view?.findViewById(R.id.percentage) + preview?.isInvisible = model.state == STATE_CLOSED + summary?.isInvisible = model.state != STATE_CLOSED + percentage?.isVisible = model.state != STATE_CLOSED + } + + if (model.state == STATE_CLOSED) { + + val subItems = when { + model.items.isEmpty() -> listOf(AttendanceEmpty()) + else -> model.items + } + + model.state = STATE_OPENED + items.addAll(position + 1, subItems.filterNotNull()) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + val viewType = when (holder) { + is AttendanceViewHolder -> ITEM_TYPE_ATTENDANCE + is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE + is MonthViewHolder -> ITEM_TYPE_MONTH + is SubjectViewHolder -> ITEM_TYPE_SUBJECT + is TypeViewHolder -> ITEM_TYPE_TYPE + is EmptyViewHolder -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + holder.itemView.setTag(R.string.tag_key_view_type, viewType) + holder.itemView.setTag(R.string.tag_key_position, position) + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is AttendanceViewHolder && item is AttendanceFull -> holder.onBind(activity, app, item, position, this) + holder is DayRangeViewHolder && item is AttendanceDayRange -> holder.onBind(activity, app, item, position, this) + holder is MonthViewHolder && item is AttendanceMonth -> holder.onBind(activity, app, item, position, this) + holder is SubjectViewHolder && item is AttendanceSubject -> holder.onBind(activity, app, item, position, this) + holder is TypeViewHolder && item is AttendanceTypeGroup -> holder.onBind(activity, app, item, position, this) + holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + fun notifyItemChanged(model: Any) { + startCoroutineTimer(1000L, 0L) { + val index = items.indexOf(model) + if (index != -1) + notifyItemChanged(index) + } + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt new file mode 100644 index 00000000..70be9256 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-1. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.text.TextPaint +import android.util.AttributeSet +import android.view.View +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.utils.Colors +import kotlin.math.roundToInt + +/* https://github.com/JakubekWeg/Mobishit/blob/master/app/src/main/java/jakubweg/mobishit/view/AttendanceBarView.kt */ +class AttendanceBar : View { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + + private var attendancesList = listOf() + private val mainPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).also { + it.textAlign = Paint.Align.CENTER + } + private var mPath = Path() + private var mCornerRadius: Float = 0.toFloat() + + init { + mCornerRadius = 4.dp.toFloat() + + if (isInEditMode) + setAttendanceData(listOf( + 0xff43a047.toInt() to 23, + 0xff009688.toInt() to 187, + 0xff3f51b5.toInt() to 46, + 0xff3f51b5.toInt() to 5, + 0xffffc107.toInt() to 5, + 0xff9e9e9e.toInt() to 26, + 0xff76ff03.toInt() to 34, + 0xffff3d00.toInt() to 8 + )) + } + + // color, count + private class AttendanceItem(var color: Int, var count: Int) + + fun setAttendanceData(list: List>) { + attendancesList = list.map { AttendanceItem(it.first, it.second) } + setWillNotDraw(false) + invalidate() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + val r = RectF(0f, 0f, w.toFloat(), h.toFloat()) + mPath = Path().apply { + addRoundRect(r, mCornerRadius, mCornerRadius, Path.Direction.CW) + close() + } + } + + @SuppressLint("DrawAllocation", "CanvasSize") + override fun onDraw(canvas: Canvas?) { + canvas ?: return + + val sum = attendancesList.sumBy { it.count } + if (sum == 0) { + return + } + + canvas.clipPath(mPath) + + val top = paddingTop.toFloat() + val bottom = (height - paddingBottom).toFloat() + var left = paddingLeft.toFloat() + val unitWidth = (width - paddingRight - paddingLeft).toFloat() / sum.toFloat() + + textPaint.color = Color.BLACK + textPaint.textSize = 14.dp.toFloat() + + for (e in attendancesList) { + if (e.count == 0) + continue + + val width = unitWidth * e.count + mainPaint.color = e.color + canvas.drawRect(left, top, left + width, bottom, mainPaint) + + val percentage = (100f * e.count / sum).roundToInt().toString() + "%" + val textBounds = Rect() + textPaint.getTextBounds(percentage, 0, percentage.length, textBounds) + if (width > textBounds.width() + 8.dp && height > textBounds.height() + 2.dp) { + textPaint.color = Colors.legibleTextColor(e.color) + canvas.drawText(percentage, left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) + } + + left += width + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt new file mode 100644 index 00000000..ce49efbd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-9. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.ColorUtils +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding +import pl.szczodrzynski.edziennik.setTintColor +import kotlin.coroutines.CoroutineContext + +class AttendanceDetailsDialog( + val activity: AppCompatActivity, + val attendance: AttendanceFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "AttendanceDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: AttendanceDetailsDialogBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = AttendanceDetailsDialogBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + val manager = app.attendanceManager + + val attendanceColor = manager.getAttendanceColor(attendance) + b.attendance = attendance + b.devMode = App.debugMode + b.attendanceName.setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt()) + b.attendanceName.background.setTintColor(attendanceColor) + + b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt new file mode 100644 index 00000000..0af177f9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.AsyncTask +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.databinding.AttendanceFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import kotlin.coroutines.CoroutineContext + +class AttendanceFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceFragment" + const val VIEW_SUMMARY = 0 + const val VIEW_DAYS = 1 + const val VIEW_MONTHS = 2 + const val VIEW_TYPES = 3 + const val VIEW_LIST = 4 + var pageSelection = 1 + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) + return b.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + activity.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_attendance_config) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + AttendanceConfigDialog(activity, true, null, null) + }), + BottomSheetSeparatorItem(true), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_mark_as_read) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_ATTENDANCE, true) } + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }) + ) + activity.gainAttention() + + if (pageSelection == 1) + pageSelection = app.config.forProfile().attendance.attendancePageSelection + + val pagerAdapter = FragmentLazyPagerAdapter( + fragmentManager ?: return, + b.refreshLayout, + listOf( + AttendanceSummaryFragment() to getString(R.string.attendance_tab_summary), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_DAYS) + } to getString(R.string.attendance_tab_days), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_MONTHS) + } to getString(R.string.attendance_tab_months), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_TYPES) + } to getString(R.string.attendance_tab_types), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_LIST) + } to getString(R.string.attendance_tab_list) + ) + ) + b.viewPager.apply { + offscreenPageLimit = 1 + adapter = pagerAdapter + currentItem = pageSelection + addOnPageSelectedListener { + pageSelection = it + app.config.forProfile().attendance.attendancePageSelection = it + } + b.tabLayout.setupWithViewPager(this) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java similarity index 68% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java index d28aadf9..c46b3e09 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java @@ -21,11 +21,8 @@ import androidx.core.graphics.ColorUtils; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; -import com.danimahardhika.cafebar.CafeBar; -import com.mikepenz.iconics.IconicsColor; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import java.text.DecimalFormat; @@ -37,25 +34,24 @@ import antonkozyriatskyi.circularprogressindicator.CircularProgressIndicator; import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.modules.attendance.AttendanceFull; -import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject; +import pl.szczodrzynski.edziennik.data.db.entity.Subject; +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; import pl.szczodrzynski.edziennik.databinding.FragmentAttendanceBinding; +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration; import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Week; import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.TYPE_RELEASED; -import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK; -import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_VULCAN; -import static pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.TYPE_ATTENDANCE; +import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED_EXCUSED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED; +import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_VULCAN; +import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; -public class AttendanceFragment extends Fragment { +public class AttendanceFragment_ extends Fragment { private App app = null; private MainActivity activity = null; @@ -79,8 +75,6 @@ public class AttendanceFragment extends Fragment { return null; app = (App) activity.getApplication(); getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true); - if (app.profile == null) - return inflater.inflate(R.layout.fragment_loading, container, false); // activity, context and profile is valid b = DataBindingUtil.inflate(inflater, R.layout.fragment_attendance, container, false); b.refreshLayout.setParent(activity.getSwipeRefreshLayout()); @@ -89,16 +83,16 @@ public class AttendanceFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + if (app == null || activity == null || b == null || !isAdded()) return; activity.getBottomSheet().prependItems( new BottomSheetPrimaryItem(true) .withTitle(R.string.menu_mark_as_read) - .withIcon(CommunityMaterial.Icon.cmd_eye_check) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withOnClickListener(v3 -> { activity.getBottomSheet().close(); - AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true)); + AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ATTENDANCE, true)); Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); }) ); @@ -123,7 +117,7 @@ public class AttendanceFragment extends Fragment { popupMenu.show(); })); - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) { + /*if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) { long attendanceLastSync = app.profile.getStudentData("attendanceLastSync", (long)0); if (attendanceLastSync == 0) { attendanceLastSync = app.profile.getSemesterStart(1).getInMillis(); @@ -133,7 +127,7 @@ public class AttendanceFragment extends Fragment { CafeBar.builder(activity) .to(activity.getNavView().getCoordinator()) .content(R.string.sync_old_data_info) - .icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon2.cmd_sync).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity)))) + .icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_download_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity)))) .positiveText(R.string.refresh) .positiveColor(0xff4caf50) .negativeText(R.string.ok) @@ -153,48 +147,56 @@ public class AttendanceFragment extends Fragment { .floating(true) .show(); } - } + }*/ - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && false) { - b.attendanceSummarySubject.setVisibility(View.GONE); - } - else { - b.attendanceSummarySubject.setOnClickListener((v -> { - AsyncTask.execute(() -> { - List subjectList = app.db.subjectDao().getAllNow(App.profileId); - PopupMenu popupMenu = new PopupMenu(activity, b.attendanceSummarySubject, Gravity.END); - popupMenu.getMenu().add(0, -1, 0, R.string.subject_filter_disabled); - int index = 0; - DecimalFormat format = new DecimalFormat("0.00"); - for (Subject subject: subjectList) { - int total = subjectTotalCount.get(subject.id, new int[3])[displayMode]; - int absent = subjectAbsentCount.get(subject.id, new int[3])[displayMode]; - if (total == 0) - continue; - int present = total - absent; - float percentage = (float)present / (float)total * 100.0f; - String percentageStr = format.format(percentage); - popupMenu.getMenu().add(0, (int)subject.id, index++, getString(R.string.subject_filter_format, subject.longName, percentageStr)); - } - popupMenu.setOnMenuItemClickListener((item -> { - subjectIdFilter = item.getItemId(); - b.attendanceSummarySubject.setText(item.getTitle().toString().replaceAll("\\s-\\s[0-9]{1,2}\\.[0-9]{1,2}%", "")); - updateList(); - return true; - })); - new Handler(activity.getMainLooper()).post(popupMenu::show); - }); + b.attendanceSummarySubject.setOnClickListener((v -> { + AsyncTask.execute(() -> { + List subjectList = App.db.subjectDao().getAllNow(App.Companion.getProfileId()); + PopupMenu popupMenu = new PopupMenu(activity, b.attendanceSummarySubject, Gravity.END); + popupMenu.getMenu().add(0, -1, 0, R.string.subject_filter_disabled); + int index = 0; + DecimalFormat format = new DecimalFormat("0.00"); + for (Subject subject: subjectList) { + int total = subjectTotalCount.get(subject.id, new int[3])[displayMode]; + int absent = subjectAbsentCount.get(subject.id, new int[3])[displayMode]; + if (total == 0) + continue; + int present = total - absent; + float percentage = (float)present / (float)total * 100.0f; + String percentageStr = format.format(percentage); + popupMenu.getMenu().add(0, (int)subject.id, index++, getString(R.string.subject_filter_format, subject.longName, percentageStr)); + } + popupMenu.setOnMenuItemClickListener((item -> { + subjectIdFilter = item.getItemId(); + b.attendanceSummarySubject.setText(item.getTitle().toString().replaceAll("\\s-\\s[0-9]{1,2}\\.[0-9]{1,2}%", "")); + updateList(); + return true; + })); + new Handler(activity.getMainLooper()).post(popupMenu::show); + }); - })); - } + })); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); b.attendanceView.setHasFixedSize(true); b.attendanceView.setLayoutManager(linearLayoutManager); + b.attendanceView.addItemDecoration(new SimpleDividerItemDecoration(getContext())); - app.db.attendanceDao().getAll(App.profileId).observe(this, attendance -> { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + b.attendanceView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (recyclerView.canScrollVertically(-1)) { + b.refreshLayout.setEnabled(false); + } + if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) { + b.refreshLayout.setEnabled(true); + } + } + }); + + App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> { + if (app == null || activity == null || b == null || !isAdded()) return; if (attendance == null) { @@ -215,26 +217,26 @@ public class AttendanceFragment extends Fragment { subjectTotalCount = new LongSparseArray<>(); subjectAbsentCount = new LongSparseArray<>(); for (AttendanceFull attendance: attendanceList) { - if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED) + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.getBaseType() == TYPE_RELEASED) continue; - int[] subjectTotal = subjectTotalCount.get(attendance.subjectId, new int[3]); - int[] subjectAbsent = subjectAbsentCount.get(attendance.subjectId, new int[3]); + int[] subjectTotal = subjectTotalCount.get(attendance.getSubjectId(), new int[3]); + int[] subjectAbsent = subjectAbsentCount.get(attendance.getSubjectId(), new int[3]); subjectTotal[0]++; - subjectTotal[attendance.semester]++; + subjectTotal[attendance.getSemester()]++; - if (attendance.type == TYPE_ABSENT || attendance.type == TYPE_ABSENT_EXCUSED) { + if (attendance.getBaseType() == TYPE_ABSENT || attendance.getBaseType() == TYPE_ABSENT_EXCUSED) { subjectAbsent[0]++; - subjectAbsent[attendance.semester]++; + subjectAbsent[attendance.getSemester()]++; } - subjectTotalCount.put(attendance.subjectId, subjectTotal); - subjectAbsentCount.put(attendance.subjectId, subjectAbsent); + subjectTotalCount.put(attendance.getSubjectId(), subjectTotal); + subjectAbsentCount.put(attendance.getSubjectId(), subjectAbsent); } } private void updateList() { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + if (app == null || activity == null || b == null || !isAdded()) return; int presentCount = 0; @@ -245,13 +247,13 @@ public class AttendanceFragment extends Fragment { List filteredList = new ArrayList<>(); for (AttendanceFull attendance: attendanceList) { - if (displayMode != MODE_YEAR && attendance.semester != displayMode) + if (displayMode != MODE_YEAR && attendance.getSemester() != displayMode) continue; - if (subjectIdFilter != -1 && attendance.subjectId != subjectIdFilter) + if (subjectIdFilter != -1 && attendance.getSubjectId() != subjectIdFilter) continue; - if (attendance.type != TYPE_PRESENT) + if (attendance.getBaseType() != TYPE_PRESENT) filteredList.add(attendance); - switch (attendance.type) { + switch (attendance.getBaseType()) { case TYPE_PRESENT: presentCount++; break; @@ -276,11 +278,12 @@ public class AttendanceFragment extends Fragment { b.attendanceView.setVisibility(View.VISIBLE); b.attendanceNoData.setVisibility(View.GONE); if ((adapter = (AttendanceAdapter) b.attendanceView.getAdapter()) != null) { - adapter.attendanceList = filteredList; + //adapter.setItems(filteredList); adapter.notifyDataSetChanged(); } else { - adapter = new AttendanceAdapter(getContext(), filteredList); + //adapter = new AttendanceAdapter(activity, true, null); + //adapter.setItems(filteredList); b.attendanceView.setAdapter(adapter); } } @@ -312,10 +315,7 @@ public class AttendanceFragment extends Fragment { float attendancePercentage; // in Mobidziennik there are no TYPE_PRESENT records so we cannot calculate the percentage - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && false) { - attendancePercentage = app.profile.getAttendancePercentage(); - } - else if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN) { + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN) { float allCount = presentCount + absentCount + belatedCount; // do not count releases float present = allCount - absentCount; attendancePercentage = present / allCount * 100.0f; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt new file mode 100644 index 00000000..8e68cdbb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -0,0 +1,230 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class AttendanceListFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceListFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceListFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var viewType = AttendanceFragment.VIEW_DAYS + private var expandSubjectId = 0L + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceListFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + viewType = arguments?.getInt("viewType") ?: AttendanceFragment.VIEW_DAYS + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, viewType) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addOnScrollListener(onScrollListener) + } + } + adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (adapter.items.isNullOrEmpty()) { + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + AttendanceDetailsDialog(activity, it) + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(attendance: List): MutableList { + if (attendance.isEmpty()) + return mutableListOf() + + val groupConsecutiveDays = app.config.forProfile().attendance.groupConsecutiveDays + val showPresenceInMonth = app.config.forProfile().attendance.showPresenceInMonth + + if (viewType == AttendanceFragment.VIEW_DAYS) { + val items = attendance + .filter { it.baseType != Attendance.TYPE_PRESENT } + .groupBy { it.date } + .map { AttendanceDayRange( + rangeStart = it.key, + rangeEnd = null, + items = it.value.toMutableList() + ) } + .toMutableList() + + if (groupConsecutiveDays) { + items.sortByDescending { it.rangeStart } + val iterator = items.listIterator() + + if (!iterator.hasNext()) + return items.toMutableList() + var element = iterator.next() + while (iterator.hasNext()) { + var nextElement = iterator.next() + while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1 && iterator.hasNext()) { + if (element.rangeEnd == null) + element.rangeEnd = element.rangeStart + + element.items.addAll(nextElement.items) + element.rangeStart = nextElement.rangeStart + iterator.remove() + nextElement = iterator.next() + } + element = nextElement + } + } + + return items.toMutableList() + } + else if (viewType == AttendanceFragment.VIEW_MONTHS) { + val items = attendance + .groupBy { it.date.year to it.date.month } + .map { AttendanceMonth( + year = it.key.first, + month = it.key.second, + items = it.value.toMutableList() + ) } + + items.forEach { month -> + month.typeCountMap = month.items + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = month.typeCountMap.entries.sumBy { + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value + } + val presenceCount = month.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 + else -> 0 + } + } + + month.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!showPresenceInMonth) + month.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + return items.toMutableList() + } + else if (viewType == AttendanceFragment.VIEW_TYPES) { + val items = attendance + .groupBy { it.typeObject } + .map { AttendanceTypeGroup( + type = it.key, + items = it.value.toMutableList() + ) } + .sortedBy { it.items.size } + + items.forEach { type -> + type.percentage = if (attendance.isEmpty()) + 0f + else + type.items.size.toFloat() / attendance.size.toFloat() * 100f + + type.semesterCount = type.items.count { it.semester == app.profile.currentSemester } + } + + return items.toMutableList() + } + return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt new file mode 100644 index 00000000..5f61020b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -0,0 +1,306 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.animation.Animation +import android.view.animation.Transformation +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.ColorUtils +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceSummaryFragmentBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment.Companion.VIEW_SUMMARY +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import java.text.DecimalFormat +import kotlin.coroutines.CoroutineContext + +class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceSummaryFragment" + private var periodSelection = 0 + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceSummaryFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var expandSubjectId = 0L + private var attendance = listOf() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceSummaryFragmentBinding.inflate(inflater) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, VIEW_SUMMARY) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + attendance = items + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(false) + layoutManager = LinearLayoutManager(context) + isNestedScrollingEnabled = false + } + } + adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.statsLayout.isVisible = true + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + AttendanceDetailsDialog(activity, it) + } + + b.toggleGroup.check(when (periodSelection) { + 0 -> R.id.allYear + 1 -> R.id.semester1 + 2 -> R.id.semester2 + else -> R.id.allYear + }) + b.toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked) + return@addOnButtonCheckedListener + periodSelection = when (checkedId) { + R.id.allYear -> 0 + R.id.semester1 -> 1 + R.id.semester2 -> 2 + else -> 0 + } + this@AttendanceSummaryFragment.launch { + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.statsLayout.isVisible = true + b.list.isVisible = true + b.noData.isVisible = false + } + adapter.notifyDataSetChanged() + } + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(): MutableList { + val attendance = when (periodSelection) { + 0 -> attendance + 1 -> attendance.filter { it.semester == 1 } + 2 -> attendance.filter { it.semester == 2 } + else -> attendance + } + + if (attendance.isEmpty()) + return mutableListOf() + + val items = attendance + .groupBy { it.subjectId } + .map { AttendanceSubject( + subjectId = it.key, + subjectName = it.value.firstOrNull()?.subjectLongName ?: "", + items = it.value.toMutableList() + ) } + .sortedBy { it.subjectName.toLowerCase() } + + var totalCountSum = 0 + var presenceCountSum = 0 + + items.forEach { subject -> + subject.typeCountMap = subject.items + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = subject.typeCountMap.entries.sumBy { + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value + } + val presenceCount = subject.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 + else -> 0 + } + } + totalCountSum += totalCount + presenceCountSum += presenceCount + + subject.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!false /* showPresenceInSubject */) + subject.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + val typeCountMap = attendance + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val percentage = if (totalCountSum == 0) + 0f + else + presenceCountSum.toFloat() / totalCountSum.toFloat() * 100f + + launch { + b.attendanceBar.setAttendanceData(typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + b.attendanceBar.isInvisible = typeCountMap.isEmpty() + + b.previewContainer.removeAllViews() + //val sum = typeCountMap.entries.sumBy { it.value }.toFloat() + typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(activity) + val attendanceObject = Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(activity, attendanceObject, manager)) + layout.addView(TextView(activity).also { + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 8.dp) + b.previewContainer.addView(layout) + } + + if (percentage == 0f) { + b.percentage.isInvisible = true + b.percentageCircle.isInvisible = true + } + else { + b.percentage.isVisible = true + b.percentageCircle.isVisible = true + b.percentage.setText(R.string.attendance_period_summary_format, percentage) + + val df = DecimalFormat("0.##") + b.percentageCircle.setProgressTextAdapter { value -> + df.format(value) + "%" + } + b.percentageCircle.maxProgress = 100.0 + animatePercentageIndicator(percentage.toDouble()) + } + } + + return items.toMutableList() + } + + private fun animatePercentageIndicator(targetProgress: Double) { + val startingProgress = b.percentageCircle.progress + val progressChange = targetProgress - startingProgress + + val a: Animation = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val progress = startingProgress + (progressChange * interpolatedTime) + //if (interpolatedTime == 1f) + // progress = startingProgress + progressChange + + val color = ColorUtils.blendARGB(Color.RED, Color.GREEN, progress.toFloat() / 100.0f) + b.percentageCircle.progressColor = color + b.percentageCircle.setProgress(progress, 100.0) + } + + override fun willChangeBounds(): Boolean { + return false + } + } + a.duration = 1300 + a.interpolator = AccelerateDecelerateInterpolator() + b.percentageCircle.startAnimation(a) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt new file mode 100644 index 00000000..dfe20eda --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.graphics.ColorUtils +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager + +class AttendanceView : AppCompatTextView { + + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) + + constructor(context: Context, attendance: Attendance, manager: AttendanceManager) : this(context, null) { + setAttendance(attendance, manager, false) + } + + @SuppressLint("RestrictedApi") + fun setAttendance(attendance: Attendance?, manager: AttendanceManager, bigView: Boolean = false) { + if (attendance == null) { + visibility = View.GONE + return + } + visibility = View.VISIBLE + + val attendanceName = if (manager.useSymbols) + attendance.typeSymbol + else + attendance.typeShort + + val attendanceColor = manager.getAttendanceColor(attendance) + + text = when { + attendanceName.isBlank() -> " " + else -> attendanceName + } + + setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) + 0xaa000000.toInt() + else + 0xccffffff.toInt()) + + setBackgroundResource(if (bigView) R.drawable.bg_rounded_8dp else R.drawable.bg_rounded_4dp) + background.setTintColor(attendanceColor) + gravity = Gravity.CENTER + + if (bigView) { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f) + setAutoSizeTextTypeUniformWithConfiguration( + 14, + 32, + 1, + TypedValue.COMPLEX_UNIT_SP + ) + setPadding(2.dp, 2.dp, 2.dp, 2.dp) + } + else { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setPadding(5.dp, 0, 5.dp, 0) + layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + setMargins(0, 0, 5.dp, 0) + } + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } +} + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt new file mode 100644 index 00000000..c2595885 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceCount { + var normalSum = 0f + var normalCount = 0 + var normalWeightedSum = 0f + var normalWeightedCount = 0f + + var pointSum = 0f + + var pointAvgSum = 0f + var pointAvgMax = 0f + + var normalAvg: Float? = null + var pointAvgPercent: Float? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt new file mode 100644 index 00000000..e993d785 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.utils.models.Date + +data class AttendanceDayRange( + var rangeStart: Date, + var rangeEnd: Date?, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt new file mode 100644 index 00000000..46877f63 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt @@ -0,0 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceEmpty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt new file mode 100644 index 00000000..c6d14914 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceMonth( + val year: Int, + val month: Int, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt new file mode 100644 index 00000000..b56cd62a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceSubject( + val subjectId: Long, + val subjectName: String, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt new file mode 100644 index 00000000..d6b431ec --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceTypeGroup( + val type: AttendanceType, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var percentage: Float = 0f + var semesterCount: Int = 0 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt new file mode 100644 index 00000000..3e7a1d32 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceItemAttendanceBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Week + +class AttendanceViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemAttendanceBinding = AttendanceItemAttendanceBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "AttendanceViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceFull, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val bullet = " • " + + b.attendanceView.setAttendance(item, manager, bigView = true) + + b.type.text = item.typeName + b.subjectName.text = item.subjectLongName ?: item.lessonTopic + b.dateTime.text = listOf( + Week.getFullDayName(item.date.weekDay), + item.date.formattedStringShort, + item.startTime?.stringHM, + item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) } + ).concat(bullet) + + if (item.showAsUnseen == null) + item.showAsUnseen = !item.seen + + b.unread.isVisible = item.showAsUnseen == true + if (!item.seen) { + manager.markAsSeen(item) + + val container = adapter.items.firstOrNull { + it is ExpandableItemModel<*> && it.items.contains(item) + } as? ExpandableItemModel<*> ?: return + + var hasUnseen = true + if (container is AttendanceDayRange) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + if (container is AttendanceMonth) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + + // check if the unseen status has changed + if (!hasUnseen) { + adapter.notifyItemChanged(container) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt new file mode 100644 index 00000000..b42d5e16 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemDayRangeBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class DayRangeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemDayRangeBinding = AttendanceItemDayRangeBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "DayRangeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceDayRange, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + item.rangeStart.formattedString, + item.rangeEnd?.formattedString + ).concat(" - ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE + b.summaryContainer.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE + + b.previewContainer.removeAllViews() + + for (attendance in item.items) { + if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || attendance.baseType == Attendance.TYPE_UNKNOWN) + continue + b.previewContainer.addView(AttendanceView( + contextWrapper, + attendance, + manager + )) + } + if (item.items.isEmpty() || item.items.none { it.baseType != Attendance.TYPE_PRESENT_CUSTOM && it.baseType != Attendance.TYPE_UNKNOWN }) { + b.previewContainer.addView(TextView(contextWrapper).also { + it.setText(R.string.attendance_empty_text) + }) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt new file mode 100644 index 00000000..37fe635c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.AttendanceItemEmptyBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder + +class EmptyViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemEmptyBinding = AttendanceItemEmptyBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "EmptyViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceEmpty, position: Int, adapter: AttendanceAdapter) { + + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt new file mode 100644 index 00000000..71c139ac --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemMonthBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class MonthViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemMonthBinding = AttendanceItemMonthBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "MonthViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceMonth, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + app.resources.getStringArray(R.array.material_calendar_months_array).getOrNull(item.month - 1)?.fixName(), + item.year.toString() + ).concat(" ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + + b.previewContainer.isInvisible = item.state != STATE_CLOSED + b.summaryContainer.isInvisible = item.state == STATE_CLOSED + b.percentage.isVisible = item.state == STATE_CLOSED + + b.previewContainer.removeAllViews() + + val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat() + item.typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(contextWrapper) + val attendance = Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(contextWrapper, attendance, manager)) + layout.addView(TextView(contextWrapper).also { + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 0) + b.previewContainer.addView(layout) + } + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + b.summaryContainer.isVisible = false + b.summaryContainer.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt new file mode 100644 index 00000000..e439111d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AttendanceItemSubjectBinding +import pl.szczodrzynski.edziennik.setText +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class SubjectViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemSubjectBinding = AttendanceItemSubjectBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "SubjectViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceSubject, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = item.subjectName + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) + + b.percentage.isVisible = true + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt new file mode 100644 index 00000000..a2f8b689 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemTypeBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class TypeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemTypeBinding = AttendanceItemTypeBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "TypeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceTypeGroup, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val type = item.type + b.title.text = type.typeName + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.details.text = listOf( + app.getString(R.string.attendance_percentage_format, item.percentage), + app.getString(R.string.attendance_type_yearly_format, item.items.size), + app.getString(R.string.attendance_type_semester_format, item.semesterCount) + ).concat(" • ") + + b.type.setAttendance(Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ), manager, bigView = false) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java deleted file mode 100644 index f159282a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java +++ /dev/null @@ -1,180 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.base; - -/* - * Copyright 2014-2017 Eduard Ereza Martínez - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import android.text.Html; -import android.util.Base64; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; - -import cat.ereza.customactivityoncrash.CustomActivityOnCrash; -import cat.ereza.customactivityoncrash.config.CaocConfig; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Themes; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.REGISTRATION_ENABLED; - -public final class CrashActivity extends AppCompatActivity { - - private App app; - - @SuppressLint("PrivateResource") - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.app = (App)getApplication(); - setTheme(Themes.INSTANCE.getAppTheme()); - - setContentView(R.layout.activity_crash); - - final CaocConfig config = CustomActivityOnCrash.getConfigFromIntent(getIntent()); - - if (config == null) { - //This should never happen - Just finish the activity to avoid a recursive crash. - finish(); - return; - } - - //Close/restart button logic: - //If a class if set, use restart. - //Else, use close and just finish the app. - //It is recommended that you follow this logic if implementing a custom error activity. - Button restartButton = findViewById(R.id.crash_restart_btn); - restartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CustomActivityOnCrash.restartApplication(CrashActivity.this, config); - } - }); - - - Button devMessageButton = findViewById(R.id.crash_dev_message_btn); - devMessageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(CrashActivity.this, CrashGtfoActivity.class); - startActivity(i); - } - }); - - final Button reportButton = findViewById(R.id.crash_report_btn); - reportButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!app.networkUtils.isOnline()) - { - new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.network_you_are_offline_title) - .content(R.string.network_you_are_offline_text) - .positiveText(R.string.ok) - .show(); - } - else - { - //app.networkUtils.setSelfSignedSSL(CrashActivity.this, null); - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?report", "CrashActivity") - .setBodyParameter("base64_encoded", Base64.encodeToString(getErrorString(getIntent(), true).getBytes(), Base64.DEFAULT)) - .run((e, result) -> { - if (result != null) - { - if (result.get("success").getAsBoolean()) { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show(); - reportButton.setEnabled(false); - reportButton.setTextColor(getResources().getColor(android.R.color.darker_gray)); - } - else { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send) + ": " + result.get("reason").getAsString(), Toast.LENGTH_LONG).show(); - } - } - else - { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send)+" JsonObject equals null", Toast.LENGTH_LONG).show(); - } - }); - } - } - }); - - Button moreInfoButton = findViewById(R.id.crash_details_btn); - moreInfoButton.setOnClickListener(v -> new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.crash_details) - .content(Html.fromHtml(getErrorString(getIntent(), false))) - .typeface(null, "RobotoMono-Regular.ttf") - .positiveText(R.string.close) - .neutralText(R.string.copy_to_clipboard) - .onNeutral((dialog, which) -> copyErrorToClipboard()) - .show()); - - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - if (errorInformation.contains("MANUAL CRASH")) - { - findViewById(R.id.crash_notice).setVisibility(View.GONE); - findViewById(R.id.crash_report_btn).setVisibility(View.GONE); - findViewById(R.id.crash_feature).setVisibility(View.VISIBLE); - } - else - { - findViewById(R.id.crash_notice).setVisibility(View.VISIBLE); - findViewById(R.id.crash_report_btn).setVisibility(View.VISIBLE); - findViewById(R.id.crash_feature).setVisibility(View.GONE); - } - } - - private String getErrorString(Intent intent, boolean plain) { - // build a string containing the stack trace and the device name + user's registration data - String contentPlain = "Crash report:\n\n"+CustomActivityOnCrash.getStackTraceFromIntent(intent); - String content = ""+contentPlain+""; - content = content.replaceAll(getPackageName(), ""+getPackageName()+""); - content = content.replaceAll("\n", "
    "); - - contentPlain += "\n"+Build.MANUFACTURER+"\n"+Build.BRAND+"\n"+Build.MODEL+"\n"+Build.DEVICE+"\n"; - if (app.profile != null && app.profile.getRegistration() == REGISTRATION_ENABLED) { - contentPlain += "U: "+app.profile.getUsernameId()+"\nS: "+ app.profile.getStudentNameLong() +"\n"; - } - contentPlain += BuildConfig.VERSION_NAME+" "+BuildConfig.BUILD_TYPE; - - return plain ? contentPlain : content; - } - - private void copyErrorToClipboard() { - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - - //Are there any devices without clipboard...? - if (clipboard != null) { - ClipData clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation); - clipboard.setPrimaryClip(clip); - Toast.makeText(CrashActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt new file mode 100644 index 00000000..3ae649f6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt @@ -0,0 +1,174 @@ +package pl.szczodrzynski.edziennik.ui.modules.base + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import cat.ereza.customactivityoncrash.CustomActivityOnCrash +import com.afollestad.materialdialogs.MaterialDialog +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.utils.Themes.appTheme +import kotlin.coroutines.CoroutineContext + +/* + * Copyright 2014-2017 Eduard Ereza Martínez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class CrashActivity : AppCompatActivity(), CoroutineScope { + companion object { + const val TAG = "CrashActivity" + } + + private val app by lazy { application as App } + private val api by lazy { SzkolnyApi(app) } + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setTheme(appTheme) + setContentView(R.layout.activity_crash) + val config = CustomActivityOnCrash.getConfigFromIntent(intent) + if (config == null) { //This should never happen - Just finish the activity to avoid a recursive crash. + finish() + return + } + + //Close/restart button logic: + //If a class if set, use restart. + //Else, use close and just finish the app. + //It is recommended that you follow this logic if implementing a custom error activity. + val restartButton = findViewById
  • ]]>
    + &nbsp;%s, lies: ja]]> + &nbsp;%s, lies: nein]]> + Antworten + %s um %s + Suche + %d Nachrichten gefunden + Nachricht wurde gesendet + Gelöscht + Posteingang + Gesendet + Mehr + Versuchen Sie, Wi-Fi oder mobile Daten einzuschalten + Sie sind offline + Weiter + Nein + Heute gibt es keine Lektionen! + Keine Berechtigungen + Nein, danke + Nicht jetzt + Keine Daten zum Verhalten der Schüler. + Andere: + %s, Punkte: %s + Loben: + Zusammenfassung - Laden… + Zusammenfassung - Semester %d + Zusammenfassung - das ganze Jahr + Warnungen: + Benachrichtigung + Abwesenheit + ntschuldigte Abwesenheit + Schulankündigung von %s: %s + %1$s auf Lektion %2$s auf Tag %3$s + %1$s am Tag %3$s + Verspätung + Entschuldigte Verspätung + Fehler beim Suchen nach Updates + Benachrichtigung über den Fortschritt der Datensynchronisation + Synchronisation + Benachrichtigungen über neue Daten im E-Klassenbuch + Benachrichtigungen + Benachrichtigungen über neue Daten im E-Klassenbuch (kein Ton - während ruhiger Zeiten) + Benachrichtigungen (Nachtruhe) + Benachrichtigungen über neue Versionen der App + App-Aktualisierungen + Benachrichtigungen über ein Problem, das eine Benutzerinteraktion erfordert (z.B. Captcha-Überprüfung). Es wird empfohlen, diese Kategorie eingeschaltet zu lassen + Aktion erforderlich + Klicken, um alle Benachrichtigungen zu sehen + Freier Tag + Updates herunterladen… + %1$s auf %2$s von %3$s + unbekanntes Subjekt + %1$s auf %2$s + Sind Sie sicher, dass Sie diese Einstellungen anwenden möchten?\n\nSie werden Informationen über einige Daten nicht sehen, so dass Sie wichtige Nachrichten oder Noten verpassen könnten.\n\nDie Einstellungen werden auf das aktuell ausgewählte Profil angewendet + Abbrechen + Szkolny.eu: Fehler + Fehler beim herunterladen von Daten für Profil %s + Erneut versuchen + Szkolny.eu: Herunterladen + Daten herunterladen + Aktualisieren + Neue Note (%s) auf %s + Hausaufgaben auf %s für %s + Hausaufgaben für %s + %s auf %s - %s + Heute ist %2$d die Glücksnummer. + Die Glücksnummer für %1$s ist %2$d. + Die Glücksnummer für morgen ist %2$d. + Du hast heute ein Glücksnummer (%2$d) + %1$s Sie werden die Glücksnummer haben! (%2$d) + Morgen ist es Ihre Glücksnummer! (%2$d) + Ungelesene Nachricht von %s: %s + Neue Benachrichtigungen: %d + Kein Update verfügbar + %s von %s auf %s + Neuer Notiz + Neues Lob + Neuer Warnung + Freigabe + %s geteilt %s auf %s - %s + %s geändert %s auf %s - %s + %s entfernt %s auf %s - %s + Neue Lehrerabwesenheit für %s + Anwesenheit + Profilarchivierung + Fehler + Nachricht vom Entwickler + Benachrichtigung + Glücksnummer + Schulankündigung + Neues Ereignis + Neue Note + Neue Hausaufgabe + Neue Nachricht + Neue geteilte Ereignis + Geteilte Hausaufgaben + Neue Abwesenheit von Lehrer + Neuer Notiz + Geteilte Ereignis entfernt + Server-Nachricht + Stundenplanänderung + Lektionsänderung + Update + Szkolny.eu: Update + Klicken Sie zum Herunterladen der Version %s + Update verfügbar + LIBRUS® Rodzina einloggen: erfordert eine Lösung für die Captcha-Aufgabe. Klicken Sie hier, um sich weiter im Tagebuch anzumelden. + Das Problem, das die Synchronisierung verhindert, muss vom Benutzer gelöst werden. Klicken Sie für weitere Informationen + Erforderliche Aktion in der App + Keine Benachrichtigungen + OK + Öffnen + Öffnen %s + Keine App, die diesen Dateityp öffnet + Andere + Bitte warten + Datenschutzerklärung + Profil %s wurde archiviert. Die Synchronisierung wurde wegen der Möglichkeit eines Datenverlusts beim nächsten herunterladen deaktiviert.\n\nSie können die in der App in diesem Profil gespeicherten Daten weiterhin anzeigen.\n\nBachten Sie, dass einige Nachrichten möglicherweise nicht korrekt heruntergeladen werden + Profil wird archiviert + Profil %s wurde archiviert, weil das Schuljahr am %s endete. Synchronisirung ist deaktiviert, aber Sie können die Daten dieses Profils trotzdem durchsuchen. + Wenn dies zu lange dauert, versuchen Sie, die App neu zu starten + Wenn dies zu lange dauert, versuchen Sie, die App neu zu starten + App laden… + Einstellungen + Profil entfernen + Sind Sie sicher? + Sie versuchen, das Profil %s zu entfernen. Das bedeutet, dass Sie die Daten dieses Profils (Noten, Stundenpläne, Ereignissen…) aus der App entfernen.\n\nSie können die meisten Daten wiederherstellen, indem Sie sich erneut einloggen.\n\nWollen Sie wirklich Profil %s entfernen? + Einen QR-Code scannen + Niemals + Später + etzt bewerten + Zeigen Sie, dass Ihnen Szkolny.eu gefällt - bewerten Sie die App und machen Sie sie noch besser! + Aktualisieren + Die Registrierung erfolgt automatisch, wenn diese Option aktiviert ist. Sie können Ereignisse erstellen und empfangen, die mit anderen Schülern in Ihrer Klasse geteilt werden. Dank dessen können Sie Elemente, die nicht vom Lehrer gespeichert wurden, zum Klassenbuch hinzufügen.\n\nStellen Sie sicher, dass Sie die Bestimmungen der Datenschutzrichtlinie lesen und die Bestimmungen akzeptieren. + Server-Registrierung + Gemeinsame Ereignisse herunterladen… + Löschen + gelöschtet + Melden + Melden Sie einen Fehler + Zurücksetzen + Speichern + Gespeichert + Keine Schulankündigungen + Nachricht senden… + Änderungsprotokoll + Oder besser nicht klicken… + Klicken Sie hier, um eine unerwartete Ausnahme auszulösen + Treten Sie unserem Discord-Server bei! + Discord Server + Hinweis. Diese Option funktioniert möglicherweise auf einigen Geräten und in einigen Teilen der App nicht. + App-Sprache ändern + Deutsch + Sprache der App + Open-Source-Lizenzen + Datenschutzrichtlinie + E-Klassenbuch + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - 2020 + Klicken Sie hier, um nach Aktualisierungen zu suchen + Aktualisierung + Version + Mehr + Benachrichtigungsweiterleitung + Bild ändern + Name ändern + Geben Sie einen Namen für das Profil ein + Ändern Sie den Profilnamen + Profil + Deaktivieren Sie bestimmte Arten von Benachrichtigungen + Benachrichtigungen filtern + Benachrichtigungen über dieses Profil anzeigen + Benachrichtigung über neue Daten + Profilbild entfernen + Entfernt das Profil und alle seine Daten + Abmelden / Profil entfernen + Angemeldet als %s + Schließen Sie dieses Profil bei der Synchronisierung ein + Synchronisieren dieses Profil + Einige Funktionen wie Ereignisfreigabe oder Benachrichtigungsweiterleitung können nicht mehr verwendet werden.\n\nDie Funktion kann je nach ausgewähltem Profil aktiviert / deaktiviert werden. + Deaktivieren der Registrierung + Ein Fehler ist aufgetreten, aber Sie sollten sich nicht darum kümmern + Beendet: Erfolg + Benutzerregistrierung aufheben… + Deaktivieren der Registrierung + Durch die Verwendung der Funktion stimme ich den unten in den Einstellungen verfügbaren Datenschutzbestimmungen zu.\n\nDie Funktion kann je nach ausgewähltem Profil aktiviert / deaktiviert werden. + Registrierung erlauben + Nutzen Sie die zusätzlichen Funktionen der App + Registrierung erlauben + Durchschnittswerte beider Semester durchschnittlich + Durchschnitt beider Semester + Endnote von 1.sem + Durchschnitt von 2. sem + Note von 1.sem + Durchschnitt von 2. sem + Durchschnitt von 1. sem + Endnote von 2. sem + Durchschnitt von 1. sem + Note von 2. sem + Der Durchschnitt der Endnoten beider Semester + Durchschnitt der beiden Endnoten + Durchschnitt aller Noten aus beiden Semestern + Durchschnitt aller Noten + Wie berechnet man den Jahresenddurchschnitt?\n\nSie können für jedes Profil eine andere Option festlegen. + Berechnung des Jahresenddurchschnitts + Berechnung des Jahresenddurchschnitts + Aktuelle Zeit: (nicht eingestellt) + Aktuelle Zeit: %s + Passen Sie die Glockensynchronisation an + Beim Warten auf das Ende der Lektion + Zählen Sie die Zeit in Sekunden herunter + Zählen Sie die Note 0 nicht zum Durchschnitt + Dieses E-Register ist noch nicht implementiert + Sie erhalten keine geteilten Ereignisse mehr und können sie nicht mehr teilen. + Sie erhalten geteilte Ereignisse von anderen Personen in Ihrer Klasse + Teile Tests in deiner Klasse + Aktivieren Sie die Ereignisfreigabe + Zeigen Sie die Abwesenheiten von Lehrern im Zeitplan an + Schwarzes Brett + Anwesenheiten + Klasse freie Tage + Klassenzimmern + Ereignisse/Zeitplan + Noten + Hausaufgaben + Glücksnummer + Nachrichten - empfangen + Nachrichten - gesendet + Warnungen/Lob + Punktnoten + Eltern-Lehrer-Treffen + Schulfreie Tagen + Lektionsänderungen und Absagen + Abwesenheiten von Lehrern + Stundenplan + Alle 7 Tage heruntergeladen + Dies kann die Datensynchronisation beschleunigen + Aktivieren / Deaktivieren von Synchronisierungselementen + Passen Sie App-Benachrichtigungen an, z. B. Ton oder Vibration + Einstellungen für Systembenachrichtigungen + Nachtruhe + Startzeit einstellen + Endzeit einstellen + Deaktiviert + Keine Benachrichtigung ertönt von %s bis %s + eine Benachrichtigung ertönt von %s bis %s am nächsten Tag + Nachtruhe + Wann sollen Daten aus dem E-Register synchronisiert werden? + Automatische Synchronisierung + Deaktiviert + Daten alle %s herunterladen + Automatische Synchronisierung + Synchronisation und Benachrichtigungen + Über App-Aktualisierungen benachrichtigen + Benachrichtigungen auf Ihrem PC anzeigen + Benachrichtigungsweiterleitung + Beschränkt die Verwendung von Mobilfunkdaten + Nur über Wi-Fi synchronisieren + App-Hintergrund entfernen + App-Hintergrund festlegen + Bitte, tun Sie das nicht + App-Hintergrund ändern + Hintergrund der Menüüberschrift wiederherstellen + Hintergrund für Menüüberschrift festlegen + Hintergrund der Menüüberschrift ändern + Hinweis. Einige Funktionen sind möglicherweise in einigen E-Registern nicht verfügbar + Mini-Menü Tasten + Passen Sie die Mini-Menüschaltflächen an + Ein Menü auf der linken Seite anzeigen + Mini-Menü anzeigen + Öffnen Sie das Menü mit der Zurück-Taste + Schlittenfahrt im Schnee + Jingle Bells, Jingle Bells + Rosa + System + Thema + Aussehen + Teilen + Teilen über… + Daten Teilen… + Zeitplan + Noten + Hausaufgaben + Nachrichten + Stundenplan + Ein Fehler ist aufgetreten + Nach Datum + Nach Thema + Alle Fächer + Semester 1 + Semester 2 + Ganzes Jahr + Sicher! + Autorisierung + Benachrichtigungen erstellen + Kontoinformationen herunterladen + Kontoliste herunterladen + Zertifikat herunterladen + Tokens aktualisieren + Anmelden… + Datenkonvertierung + Push-Benachrichtigungen konfigurieren + Synchronisieren + Kontoinformationen herunterladen + Schulankündigungen herunterladen + Anwesenheit herunterladen + Anwesenheitskategorien herunterladen + Kategorie der Schulnoten aus Verhaltens herunterladen + Schulnoten aus Verhaltens herunterladen + Zeitplan herunterladen + Klasseninformationen herunterladen + Klassenzimmer herunterladen + Kategorie von Beschreibende Schulnoten herunterladen + Beschreibende Schulnoten herunterladen + Wörterbuch herunterladen + Ereigniskategorien herunterladen + Ereignisse herunterladen + Testen herunterladen + Notenkategorie herunterladen + Notenkommentare herunterladen + Notendetails herunterladen + Noten herunterladen + Hausaufgaben herunterladen + Glücksnummer herunterladen + Nachrichten herunterladen + Empfangene Nachrichten herunterladen + Gesendete Nachrichten herunterladen + Notizendetails herunterladen + Notizen herunterladen + Kategorien von Punktnoten herunterladen + Punktnoten herunterladen + Vorschlagene Noten herunterladen + Treffen mit Eltern herunterladen + Freien Schultagen herunterladen + Schulinformationen herunterladen + Geteilte Ereignisse herunterladen + Schulfächer herunterladen + Abwesenheit von Lehrern herunterladen + Abwesenheitsklassen für Lehrer herunterladen + Klassengruppen herunterladen + Stundenplan herunterladen + Stundenplan änderungen herunterladen + Lehrerliste herunterladen + Daten aktualisieren… + Laden… + synchronisiert + Nicht übereinstimmender Name des Benutzerkontos. Melden Sie einen Fehler. + Anwendungsserverfehler + Das ausgewählte Klassenbuch ist archiviert + Anhang kann nicht heruntergeladen werden. Das Netzwerklaufwerk mit dem Anhang ist nicht verfügbar. + Melden Sie einen Fehler + Synchronisierungsfehler + Ungültiges Gerät + Ungültige Anmeldedaten + Ungültiger Schulname oder verbotene Zeichen + Die Serveradresse ist ungültig. Stellen Sie sicher, dass es korrekt eingegeben wurde und keine Leerzeichen enthält. + Ungültiges Token angegeben. + Das LIBRUS® Rodzina-Konto hat die Verbindung zum LIBRUS® Synergia-Konto verloren. Melden Sie sich bei portal.librus.pl oder der offiziellen "LIBRUS®" App an und befolgen Sie die Anweisungen zur Reparatur Ihres Kontos. + Das LIBRUS® Rodzina-Konto wurde nicht aktiviert. Aktivieren Sie das Konto mit der E-Mail, die Sie erhalten haben. + Anmeldefehler + Technische Pause\n\nVersuchen Sie es später erneut + API-Adresse nicht gefunden. Versuchen Sie erneut, sich am Gerät anzumelden. + Keine Internetverbindung\n\nMögliche E-Klassenbuch-Server sind überlastet oder haben eine technische Pause. + Login erfolgreich + Altes Passwort wurde verwendet + Dieses Profil wurde archiviert, daher ist eine Synchronisierung nicht möglich. + Profil nicht gefunden. Versuchen Sie, sich abzumelden und erneut anzumelden. + Diesem Konto sind keine Schüler zugeordnet.\n\nStellen Sie sicher, dass der Schüler Ihrem Konto zugewiesen ist. Überprüfen Sie die Desktop-Version der E-Klassenbuch-Website. + Student %s ist diesem Konto im Klassenbuch nicht zugeordnet (Login: %s).\n\nVersuchen Sie erneut, sich anzumelden, obwohl dies möglicherweise auf eine technische Unterbrechung im E-Klassenbuch hinweist. + Fehler beim Speichern der Daten. Melden Sie den Fehler. + Fehler beim Herstellen einer sicheren Verbindung. + Das LIBRUS® Synergia-Konto wurde nicht von einem Elternteil oder Erziehungsberechtigten aktiviert.\n\nLoggen Sie sich in portal.librus.pl ein und befolgen Sie die Anweisungen, um es zu aktivieren. + Auszeit + Unbekannter Fehler + Stundenplan herunterladen + Alles + Ansagen herunterladen… + Anwesenheits herunterladen… + Noten herunterladen… + Hausaufgaben herunterladen… + Empfangene Nachrichten herunterladen… + Gesendete Nachrichten herunterladen + Notizen herunterladen… + Alles synchronisieren… + Wählen Sie aus, welche Elemente synchronisiert werden sollen. + Stundenplan synchronisieren… + Manuelle Synchronisierung + Login Fehler + Die neuesten Daten werden möglicherweise nicht angezeigt + Melden + API-Antwort einschließen (empfohlen) + Fehler melden + Synchronisierungsdienst + Klicken Sie auf das Symbol, um alle Daten als gelesen zu markieren. + Benachrichtigungen + Klicken Sie auf die Menüüberschrift, um das Profil eines Schülers zu ändern oder hinzuzufügen. Hier finden Sie auch eine manuelle Synchronisierung aller Profile. + Profile ändern + Klassenlehrer + Bibliothekar + Andere + Elternteil + Elternrat + Pädagoge / Psychologe + Schulleiter + Schulverwalter + Schulelternrat + Sekretariat + Spezialist + Schüler + Administrator / Superadministrator + Lehrer + Kategorie durchsuchen + Übermorgen + Vorgestern + Bernstein + Schwarz / OLED + Blau + Schokolade + Dunkel + Dunkelblau + Dunkelgrün + Dunkelviolett + Dunkelrot + Indigo + Hell + Hellblau + Hellgrün + Hellviolett + Hellrot + Hellgelb + Violett + rot + Stundenplan anzeigen + Kein Lektionen an diesem Tag: + Freier Tag + Mit Änderungen für diese Woche (%s bis %s) + Druckbar (keine Änderungen, weniger Farben) + Namen des Schülerprofils anzeigen + Mit Änderungen für die nächste Woche (%s bis %s) + Keine Stundenplanänderungen + Druckbar (weniger Farben) + Dies kann einige Sekunden dauern… + Stunden erstellen + Der Umfang des generierten Plans + Für ausgewählte Woche + Profilnamen anzeigen + Namen von Lehrern anzeigen + Der Stundenplan wurde im Verzeichnis Szkolny.eu als Bilddatei gespeichert. \ N \ nSie können ihn sofort öffnen oder freigeben. + Gemacht! + Lektion abgesagt + Ersatz + Ersatz: anstelle von %s + Die Lektion wurde auf einen anderen Tag verschoben + Die Lektion wurde von einem anderen Tag verschoben + Die Lektion wurde von %s %s verschoben + Die Lektion wurde von %s verschoben + "Die Lektion wurde auf %s %s verschoben" + Die Lektion wurde auf %s verschoben + Datum + Wählen Sie das Datum + Wählen Sie das Thema aus + Zeit + Wählen Sie die Zeit + %s (Lektion %d) + Für diesen Tag wurden keine eigenen Lektionen hinzugefügt. + Bei jeder %s Lektion + Beinhaltet einen Stundenplan für jede Woche + Nach Schulfach + Einmal + Wiederholen + (kein Klassenzimmer) + Keinen Lektionen an diesem Tag + Keinen Lektionen an diesem Tag + (kein Name) + Synchronisieren + Der Stundenplan für diese Woche wurde noch nicht synchronisiert + Kein Stundenplan + für Woche %s + Wenden Sie sich an Ihren Tutor, um den Stundenplan zu erhalten. + Der Zeitplan ist nicht öffentlich + Kein Zeitplan + Datum auswählen + Synchronisierung Stundenplan für die ausgewählte Woche… + Heute + Alles synchroniesieren + Debuggen + Feedback + Entwürfe + Empfangene Nachrichten + Gesendeten Nachrichten + Müll + Profilmanager + Wechseln Sie das Semester + Profil ändern… + Heute + Morgen + Synchronisieren… + Unbekannt + Update erforderlich + Sie müssen die App aktualisieren, um sie verwenden zu können. Klicken Sie zum Herunterladen auf OK. + Sie versuchen, eine ältere Version der App auszuführen, deren Daten möglicherweise beschädigt werden. + Gepaarte Browser + Keine Verbindung zum Server. + Gepaart %s + Keine gepaaren Browser. + paaren Sie den Browser + Möchten Sie diesen Browser wirklich löschen? + Das Token sieht nicht gültig aus + Das kannst du nicht machen + trennen + Sie müssen die automatische Registrierung aktivieren, um die Benachrichtigungsweiterleitung verwenden zu können.\n\nWählen Sie in den Anmeldeeinstellungen \"Registrierung zulassen\". + Was willst du machen? + Was ist das? + Was ist neu? + Passen Sie das Widget an + Kombiniert - alle Profile + Wird geladen… + Kein Profil + Glücksnummer + Keine Benachrichtigungen + Benachrichtigungen + Aktualisieren + Synchronisieren + Verwenden Sie eine größere Schriftart + Keinen Lektionen am ausgewählten Tag. + Keine Lektionen für den nächsten 7 Tage. + Der Stundenplan wurde nicht heruntergeladen.\n\nÖffnen Sie die App und synchronisieren Sie sie, um den Stundenplan herunterzuladen. + Das ausgewählte Profil ist nicht vorhanden.\nLöschen Sie das Widget und versuchen Sie es erneut. + Keine Lektionen für den nächsten 7 Tage. + Der Zeitplan wurde nicht heruntergeladen. + Stundenplan + Kombinierter Stundenplan + Ja + gestern + Sie sind offline. Versuchen Sie, Wi-Fi oder mobile Daten zu aktivieren. + Netzwerkverbindung + (Kind) + (Elternteil) + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index f6ec7c2a..58de31ae 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1,20 +1,22 @@ + Semester 1 Semester 2 - - - From newest - By subject name - - From e-register By grade\'s value - + + From newest + By subject name + + + Event + Homework + January February @@ -29,38 +31,32 @@ November December - - - Event - Homework + + January + February + March + April + May + June + July + August + September + October + November + December - Szkolny.eu - - Light - Dark - Black / OLED - Chocolate - Indigo - Light yellow - Dark blue - Blue - Light blue - Dark purple - Purple - Light purple - Dark red - Red - Light red - Dark green - Amber - Light green - Abort Add Timetable changes + Absent teachers %s ago + There may be an app manager running on this device, which could cause issues with automatic synchronisation.\n\nIn order to prevent this issue, turn off battery optimization for Szkolny.eu.\n\nPress OK to go to system settings. + There was a problem with synchronization + Couldn\'t open settings Are you sure? + Summary - semester %d  + Summary - all year  Absences: Of which unexcused: Late: @@ -69,18 +65,24 @@ Releases: All subjects Summary - loading… - Summary - semester %d  - Summary - all year  Back Adjust the bell sync time. Format: ±H:MM:SS Incorrect format + break (%s) Calibration is impossible, because there are no lessons now. Try again, i.e. in the end of the lesson or break. Remember that you should run this even before planned bell time. + Choose the nearest bell to synchronize. \n\nCurrent calibration: %s - Click OK, when the lesson ends. The counter\'s time will be calibrated to the bell time.\n\nPlanned bell time is %s + Click the bell icon, when the bell rings. The counter\'s time will be calibrated to the bell time.\n\nPlanned bell time is %s\n\nThe bell time difference at the moment is %s + lesson %s (%s) Do you want to reset the calibration? - The bell is inexact by %s%s + The bell is inexact by %s + Time of the bell to synchronize Calibrate with the school bell + Calendar app not found Cancel + Move up + Move down + Upcoming events Go to grades Grades - last 7 days No grades to show @@ -102,21 +104,29 @@ Timetable for %s The lessons are over! You should never see this! Report an error! + Upcoming events + Recent grades + Lucky number + Timetable Download You have version %s, while %s is available. An update is available Choose a file Choose a profile Close + Compose + Compose + Configure Copied to clipboard Copy to clipboard + Synchronize If the app doesn\'t open at all, you can try to download the data again. Click the button, then Restart the app. Error details Message from developer It\'s not a bug. It\'s a feature! Please, report this error. Report the error - "Couldn't send a report: " + "Couldn\'t send a report: " Error report sent Restart application Synchronise @@ -124,8 +134,10 @@ today (%s) tomorrow (%s) You shouldn\'t really care about what you see here. - Expected semester %d average:\n%#.2f\n - Expected yearly average:\n%#.2f\n + These settings are intended for this app\'s developers, not for normal users.\n\nThey are not described in any way, so using them may lead to breaking some functions in the app, damaging your system, losing data or even installing a virus on the battery.\n\nBe careful and use them wisely. + Developer mode + Predicted semester %d average:\n%#.2f\n + Predicted yearly average:\n%#.2f\n Semester %d final grades average:\n%#.2f\n Yearly final grades average:\n%#.2f\n Semester %d proposed grades average:\n%#.2f\n @@ -133,6 +145,16 @@ Grade averages Averages of semester %d are not available Yearly average is not available + %s - %s (%s lessons - %s hours %s minutes) + No events this day + Error details + Added + Attachments + Content + Subject + Teacher + Team + Topic No events in the selected period. All day You cannot share this event unless you choose a group (class) of recipients. @@ -140,14 +162,23 @@ Select a lesson -- custom time -- Date + Select the time Date + next lesson %s + next %s (%s) + -- other day -- + today (%s) + tomorrow (%s) Lesson Select a lesson + More options To be able to share anything, you have to agree to register.\nDon\'t worry, the registration is automatic, so you don\'t have to specify any data.\n\nClick More to learn more about the processed data. You are not registered + No lessons this day No subject No teacher No team + No timetable is downloaded… This event was shared by someone else. You will remove it only on your device, but you\'ll never receive it again. This event was shared by you, so it\'ll be also removed on your classmates\' devices. Share to your class @@ -161,6 +192,9 @@ Teacher Team Select a team + Lesson/time + Select the time + Add an entry to the agenda Topic Specify a topic Type @@ -172,7 +206,7 @@ Description Grade history Grade ID - Grade is hidden - it was improved + The grade is improved by another one (no category) (no description) semester %d @@ -180,17 +214,36 @@ Value to the average weight %.2f Grades color + By grade\'s value + From e-register + From newest + By subject name Sort grades Details + Classroom + Lesson ID + Lesson number + Lesson shifted from %s + Lesson shifted to %s + Teacher + Team + Show + No events on this lesson. + You can add a new event manually, using Add button. + Choose which notifications should appear in your system and the app. + Show selected notifications + Profile removed successfully. Do you want to remove the selected event? Manual event Manual homework + Manual synchronization Profile Big font Opacity Theme Do nothing Done + Don\'t ask again Download %s (%s)? Downloading… Downloading file @@ -199,9 +252,78 @@ Create new Open existing Overwrite + Add a new profile Export app data + Add or remove students\' profiles + There are no more lessons for this subject. Download the timetable and try again. + Custom subject + Cancel + Close + Reporting errors… + Reporting errors + Synchronization interrupted + First login + Creating notifications + Synchronizing profile %s… + Thanks to it, Szkolny.eu can synchronize data with an e-journal. You can close it because it isn\'t doing anything right now. + Synchronization service + Syncing account details… + Syncing announcements… + Syncing attendance… + Syncing presence categories… + Syncing student behaviour… + Syncing behaviour grades… + Syncing calendar… + Syncing class information… + Syncing classrooms… + Syncing data… + Syncing descriptive grades… + Syncing dictionaries… + Syncing event categories… + Syncing calendar events… + Syncing exams + Syncing grade categories… + Syncing grade comments… + Syncing grades… + Syncing homework… + Syncing lessons… + Syncing the lucky number… + Syncing messages… + Syncing messages inbox… + Syncing messages outbox… + Syncing notice types… + Syncing notices… + Syncing point grades… + Syncing proposed grades… + Syncing parents\' meetings… + Setting up push notifications… + Syncing school info… + Syncing student info… + Syncing subjects… + Syncing teacher absence types… + Syncing teacher absences… + Syncing teachers… + Syncing class groups… + Syncing timetable… + Syncing unit info… + Logging in to Edudziennik… + Logging in to iDziennik… + Logging in to iDziennik… + Logging in to the API… + Logging in to LIBRUS® messages… + Logging in to LIBRUS® Rodzina… + Logging in to LIBRUS® Synergia… + Logging in to MobiDziennik… + Logging in to MobiDziennik… + Logging in to MobiDziennik… + Logging in to Template API… + Logging in to Template WEB… + Logging in to VULCAN® + Syncing shared events… + Creating notifications… Error Error details + Wrong module given: %d Report it to the application developer Your iDziennik password has expired. You have to change it in the desktop version of the website. The subject ID wasn\'t specified. An error occurred! @@ -212,22 +334,44 @@ School year was not found. Editing others\' events is not implemented yet. Student ID was not found. Probably the app was updated.\n\nDo a sync again or try again in 5 minutes. + Kod błędu: %d (on %s) Added: all day class event + %1$s by %2$s + %1$s by you + {cmd-share-variant} %1$s by %2$s + {cmd-share-variant} %1$s by you essay exam school trip information + Added %1$s by %2$s%3$s + Added %1$s by you%3$s + Adde %1$s%3$s + {cmd-share-variant} %1$s by %2$s%3$s + {cmd-share-variant} %1$s by you%3$s + You need to turn on server registration, in order to share an event. This option lets you create and receive shared events in your class.\n\nIt will be turned on automatically by pressing OK.\n\nBefore turning it on, make sure you\'ve read the terms and accept them + Sharing events + Removing event… + Saving event… + Sharing event… + Removing event from the rest of the class… + Removing shared event… + Are you sure you want to mark this task as completed?\n\nIt won\'t show up on the main or homework pages, but will still be available in the timetable + Mark as done other project parent-teacher meeting reading + Removing shared event… {cmd-share-variant} by %s you Event sharing + Sharing the event… short test homework + There are no more events in the calendar. Exit Frequently asked questions Did you find an answer to your question? If not, you can ask me directly. @@ -246,14 +390,28 @@ %s - proposed Starting points Starting points: semester %d + from %s %s (yearly) %s - yearly final %s (prop. yearly) %s - yearly proposed %spts, %spts - %s - %s%% + no average + average: %s + points: %s %spts + sum: %s + %s pts + annual: %2$s + Count the average if all weights are 0 + Allows to calculate the average of subjects in which all grades have a weight of 0 (are not counted to the average).\n\nIf such subjects shouldn\'t be counted to the average, uncheck the box next to this setting. + Exclude selected grades from the average + Separate the grades with a comma + Enter grades… + Hide corrected grades from the list + Custom \"minus\" value + Custom \"plus\" value + Grade configuration Add a grade Adding a grade Choose the weight of the grade @@ -270,40 +428,107 @@ other weight (resit) %s max %s pts - No subjects to show + No grades. + Semester %d Semester 1 Semester 2 semester %d: %s semester %d: %s%% semester %d: %spts + Semester %d Semester %d + Current settings could be affecting the average. If you think that it\'s incorrect, click Configure. + There is a custom \"minus\"/\"plus\" value set. If you think that it\'s incorrect, click Configure. + *grade averages are only illustrative and may vary, depending on school settings + *predicted average + *from final grades\nPredicted: %s + *from final grades + *from proposed grades\nPredicted: %s + *from proposed grades + The predicted grade for a given subject is calculated based on the current weighted average.\n\nThe grade is an integer that the teacher would have based on the average. The number is rounded up if the decimal part exceeds 75.\nFor example: 3.75 as well as 4.74 give a 4.\n\nThe predicted average of all subjects includes the final grades calculated in this way. + Calculating the average of all subjects + Too little data to calculate the average. + Average of regular grades + Average of point grades + Proposed grades average:\n%s + semester 1 + semester 2 + Grade stats + yearly value: %s weight %s not counted to avg end of year: %s yearly: %s%% yearly: %spts + Entire year: %s • %s Hello blank fragment Help Notification forwarding allows you to pair a PC web browser to receive notifications on your desktop. This includes new grades, events, homework etc.\n\nClick \"Notification forwarding\" to begin. - Registraton will run automatically on the first login.\n\nThere will be some data sent to the app server:\n- your school and class ID\n- your e-register username\n- your first and last name\n\nThe only data visible to others is your name (when sharing events). Any private data (like password, grades etc.) won\'t be sent anywhere. Learn more in the Privacy policy. + Registration will run automatically on the first login.\n\nThere will be some data sent to the app server:\n- your school and class ID\n- your e-register username\n- your first and last name\n\nThe only data visible to others is your name (when sharing events). Any private data (like password, grades etc.) won\'t be sent anywhere. Learn more in the Privacy policy. + Download again + Edit event + Go to timetable + Mark as done + Save to calendar + Add/remove cards + Swipe left to remove a card, hold to move. + %s • Your e-register number is %d + %s • Click to set your journal number. + The lucky number on %s is %d. + No info about the lucky number. + There is no lucky number today. + %d is today\'s lucky number. + %d is the lucky number for tomorrow. + Your number will be lucky on %s. + Today is your lucky number! + Tomorrow will be your lucky number! + Later: + no lessons + First: %s + Coming up: %s + Now: %s + %d lessons - %s to %s + Remaining lessons: %d to %s + There are no lessons + There are no lessons in the coming week. + The timetable was not downloaded + Sync + The timetable for week %s hasn\'t yet been downloaded. + There is no timetable + The timetable hasn\'t yet been made public by your school.\n\n\Contact your tutor for more information. + Today + Tomorrow (%1$s) Edit + There is no homework. + Current + Past I agree I disagree + in %s Incorrect format + Use system language New lesson Break Lesson cancelled Lesson change + Shifted lesson Timetable change + There are no more lessons today! Loading… + This account doesn\'t have any students + You can\'t log in, because there is no student assigned to this account.\n\nAssing a student on e-journal page or login with an account with a student already assigned to it. Allow registration Log in Do you really want to cancel? Displayed profiles won\'t be saved. + Use the account details, which you use to login into the desktop version of Edudziennik. + Log in - Edudziennik Error: %s Account is not activated Token has expired. Generate a new token. Incorrect server name + Incorrect code + Incorrect code or PIN Incorrect e-mail Incorrect login Incorrect login or password @@ -312,9 +537,11 @@ Incorrect school name Incorrect school symbol Incorrect token + Incorrect token or PIN Incorrect username Enter server address no arguments specified + Enter the code Enter e-mail Enter login Enter password @@ -345,10 +572,13 @@ Help - iDziennik Progman Use the data which you got from your school. In case of any problems, use the button below the form. Log in - iDziennik Progman - In order to use the app you need a Librus account. You can create it on portal.librus.pl. Use the data you normally enter in the fields marked on the image. - Help - Librus - Log in using your Librus account data (created before on your e-mail address). You can\'t login using the data provided by your school. In case of any problems, use the button below the form. - Log in - Librus + LIBRUS® - login + In order to use the app you need a LIBRUS® Rodzina account. You can create it on portal.librus.pl. Use the data you normally enter in the fields marked on the image. + Help for LIBRUS® login + Log in with the token and PIN, which you can get from the "mobile apps" section of LIBRUS® Synergia. + Log in to LIBRUS® JST + Log in using your LIBRUS® Rodzina account data (created before on your e-mail address). You can\'t login using the data provided by your school. In case of any problems, use the button below the form. + Log in to LIBRUS® Synergia A migration error has occurred. It\'s already reported, which means I\'ll try to fix it.\n\nYou can continue using the app. In case of any problems try to remove the profile and create it again. The app got a huge update. If you encounter any problems, feel free to contact me using the Feedback option in the menu. Szkolny.eu has been updated @@ -362,6 +592,8 @@ Logging in… Choose the e-register which your school uses. Later you\'ll be able to add more accounts using different e-registers. Which e-register do you use? + (child) + (parent) Add a student You have to select at least one profile to save in the app. No profile selected @@ -369,17 +601,17 @@ It\'s almost ready You won\'t be able to use Event sharing and Notification forwarding. Turn off registration? - An error occurred during sync. Click the button on the bottom to report the error.\n\nProfiles were saved, so the app should work correctly. In case of any problems you can go to Feedback in the Drawer.\n + An error occurred during sync. Click the button on the bottom to report the error.\n\nProfiles were saved, so . In case of any problems you can go to Feedback in the Drawer.\n Sync error - %s]]>
    + Syncing profile Syncing… Registering the device: - Login into desktop version of Vulcan e-register and select \"Dostęp mobilny\" option. Click \"Zarejestruj urządzenie mobilne\". You can type in the data or scan the QR code and type only the PIN. - Help - Vulcan UONET+ + Login into desktop version of VULCAN® e-register and select \"Dostęp mobilny\" option. Click \"Zarejestruj urządzenie mobilne\". You can type in the data or scan the QR code and type only the PIN. + Help - VULCAN® UONET+ Obtained data: Scan QR code Choose \"Dostęp mobilny\" on e-register website, register a new device and enter the data. In case of any problems, use the button below the form. - Log in - Vulcan UONET+ + Log in - VULCAN® UONET+ Add… Change view Generate a block timetable @@ -398,39 +630,92 @@ Today T W + Add event + Save own event or homework in the agenda Add a new student + Add or remove cards Agenda + Change Agenda view Notice board Attendance + Debugging + Help and FAQ + Save the timetable as an image + Generates timetable for the whole week Grades + End-of-year average calculation method + Grade averages + Show semester and end-of-year averages + Grades color + Grades settings Grade editing simulator + Sort grades Help Home page Homework + Lab Manage profiles Mark as read + Mark everything as read + Message + Compose Messages - Inbox - Sent Behaviour Notifications + Clear + Cleared notifications + Set your register number Settings Synchronise Synchronise all + Template Timetable + Timetable editor + Forwarding notifications Cannot download attachment An internal error occurred while downloading the attachment. A weak internet connection may be the case. + Download again + Are you sure you want to send the message to selected recipients? + Confirm sending the message + Add an attachment + Abort message + Save draft + Send + This recipient has already been selected + Select recipients + Check if the selected recipients are correct + Select recipients from the category %s you want to add to the message. + Add recipients + Send + Enter a subject with at least 3 characters + Subject + Enter the message + Write a message… + Write a message + To + Are you sure you want to delete the message? + This will move the message to the \"Deleted\" tab in the app. The changes will not affect the e-journal message (it will not be deleted ). Message downloading error Draft + You don\'t have any messages + Error getting the recipient list  %s, read: %s, %s]]>  %s, read: yes]]> -  %s]]>  %s, read: no]]> + Reply + %s at %s + Search + Found %d message + Message was sent + Deleted + Inbox + Sent More Try to turn on Wi-Fi or mobile data. You\'re offline Next No + There are no lessons today! No permissions No, thanks Not now @@ -439,29 +724,35 @@ %s, points: %s Praises: Summary - loading… - Summary - semester %d  - Summary - all year  + Summary - semester %d  + Summary - all year  Warnings: Notification Absence Excused absence - School announcement: %s + School announcement from %s: %s %1$s on lesson %2$s on day %3$s %1$s on day %3$s Late Excused late - Notification about data downloading - Downloading data - Notification about new data in the e-register + Failed to check for updates + Notification about data sync progress + Synchronisation + Notifications about new data in the e-register Notifications - Notification about new data in the e-register (no sound) - Notifications (quiet) + Notifications about new data in the e-register (no sound - during quiet hours) + Notifications (quiet hours) Notifications about new versions of the app App updates + Notifications about a problem that requires user interaction (e.g. Captcha verification). It is recommended to leave this category on. + Action required + Click to see all notifications + Day off Downloading update… %1$s on %2$s from %3$s - %1$s on %2$s unknown subject + %1$s on %2$s + Are you sure you want to apply these settings?\n\nYou won\'t see information about some data, so you may miss important messages or grades.\n\nThe settings will be applied to the currently selected profile. Cancel Szkolny.eu: error Error while getting data for profile %s @@ -472,12 +763,13 @@ New grade (%s) from %s Homework from %s for %s Homework for %s - Today %d is the lucky number. - The lucky number for %s is %d. - The lucky number for tomorrow is %d. - You are the Lucky number today! (%d) - %s you will have the Lucky number! (%d) - Tomorrow it\'s your Lucky number! (%d) + %s on %s - %s + Today %2$d is the lucky number. + The lucky number for %1$s is %2$d. + The lucky number for tomorrow is %2$d. + You are the lucky number today! (%2$d) + %1$s you will have the lucky number! (%2$d) + Tomorrow it\'s your lucky number! (%2$d) Unread message from %s: %s New notifications: %d No update available @@ -489,7 +781,7 @@ %s shared %s on %s - %s %s changed %s na %s - %s %s removed %s na %s - %s - Szkolny.eu: %s + New teacher absence for %s Attendance Profile archiving Error @@ -502,7 +794,10 @@ New homework New message New shared event + Shared homework + New teacher absence New notice + Removed shared event Server message Timetable change Lesson change @@ -510,11 +805,15 @@ Szkolny.eu: update Click to download version %s Update available + LIBRUS® Rodzina login: Captcha verification required. Click to continue logging into the e-journal. + The problem that prevents synchronization must be solved by the user. Click for more information. + Action required in the application No notifications. OK Open Opening %s No application opening this file type + Other Please wait Privacy policy Profile %s has been archived. Sync was disabled, because of the possibility of data loss on next data download.\n\nYou can still browse this profile\'s saved data.\n\nKeep in mind that some messages might not download correctly. @@ -527,11 +826,15 @@ Remove profile Are you sure? You are trying to remove profile %s. This means removing this profile\'s data (grades, timetables, events…) from the app.\n\nYou\'ll be able to restore most of the data by logging in again.\n\nDo you really want to remove profile %s? + Scan a QR code Never Later Rate now Show that you like Szkolny.eu - rate the app and make it even better! Refresh + Registration is automatic if this option is enabled. It allows you to create and receive events shared with other students in your class. Thanks to this, you can add items not saved by the teacher to the journal.\n\nMake sure you read the terms and accept them. + Server registration + Syncing shared events… Remove Removed Report @@ -544,10 +847,16 @@ Changelog Or better do not click… Click to throw an unexpected exception + Join our Discord community! + Discord server + Notice. This feature may not work on some devices or in some parts of the app. + Change app language + "English " + App language Open-source licenses Privacy policy E-register - © Kuba Szczodrzyński September 2018 - October 2019 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - 2020 Click to check for updates Update Version @@ -558,6 +867,8 @@ Enter the name Change name Profile + Turn off certain types of notifications + Filter notifications Show notifications about this profile Notify about new data Remove profile picture @@ -600,6 +911,7 @@ You will receive shared events from other people in your class. Share exams in your class Enable Event sharing + Show teacher absences in Agenda Notice board Attendances Class free days @@ -652,6 +964,9 @@ Customize Mini menu buttons Show a menu on the left Show Mini menu + Back button opens drawer + Jingle all the way + Jingle bells, Jingle bells Pink System Theme @@ -664,6 +979,9 @@ Homework Messages Timetable + An error occurred + By date + By subject All subjects Semester 1 Semester 2 @@ -671,7 +989,6 @@ Sure! Authorizing Creating notifications - %s… Getting account info Getting account list Getting certificate @@ -682,8 +999,8 @@ Syncing Getting account info Getting notice board - Getting attendance types Getting attendance + Getting attendance types Getting behaviour grades categories Getting behaviour grades Getting agenda @@ -696,6 +1013,7 @@ Getting events Getting exams Getting grade types + Getting grade comments Getting grade details Getting grades Getting homework @@ -709,6 +1027,7 @@ Getting point grades Getting proposition grades Getting parent-teacher meetings + Getting school free days Getting school info Syncing shared events Getting subjects @@ -732,7 +1051,8 @@ Invalid school name or forbidden characters Server address is invalid. Make sure it\'s entered correctly and doesn\'t contain whitespaces. Invalid token specified. - LIBRUS account is not activated. Activate it using a link in the received e-mail. + LIBRUS® Rodzina account has lost connection with the LIBRUS® Synergia account. Login on portal.librus.pl or in the "LIBRUS®" mobile app and follow the instructions to fix the problem. + LIBRUS® account is not activated. Activate it using a link in the received e-mail. Login error Maintenance API address not found. Try to login again. @@ -745,6 +1065,7 @@ Student %s is not assigned to this register (login: %s).\n\nTry to log in again although this may also indicate a maintenance time. Error while saving data. Report the error. Failed to establish a secure connection. + The LIBRUS® Synergia account hasn\'t been activated by a parent or legal guardian.\n\nLog into portal.librus.pl and follow the instructions in order to activate it. Timeout Unknown error Syncing agenda… @@ -770,20 +1091,93 @@ Notifications Click on the menu header to change or add a student\'s profile. Here you\'ll also find a manual sync of all profiles. Modify profiles + Tutor + Librarian + Other + Parent + Parents\' council + Pedagogue / Psychologist + Principal + School Administrator + School parents\' council + Secretariat + Specialist + Student + SuperAdmin + Teacher + Browse category overmorrow the day before yesterday + Amber + Black / OLED + Blue + Chocolate + Dark + Dark blue + Dark green + Dark purple + Dark red + Indigo + Light + Light blue + Light green + Light purple + Light red + Light yellow + Purple + Red + Show timetable + No lessons this day: + Free day With changes for this week (%s to %s) Printable (no changes, less colors) Display the student\'s profile name With changes for the next week (%s to %s) No timetable changes + Printable (less colors) This may take few seconds… Generating timetable Range of the timetable + For selected week + Show profile name + Show names of teachers The timetable was saved in Szkolny.eu directory as an image file.\n\nYou can open or share it right away. Done + Lesson cancelled + Lesson change + Lesson change: instead of %s + Lesson shifted to other day + Lesson shifted from other day + Lesson shifted from %s %s + Lesson shifted from %s + "Lesson shifted to %s %s " + Lesson shifted to %s + Date + Select the date + Select the subject + Time + Select the time + %s (lesson %d) + No own lessons added for this day. + On each %s lesson + Includes a timetable for each week + By subject + Once + Repeating (no classroom) No lessons this day + No lessons this day + (no name) + Synchronize + Timetable for this week hasn\'t been synchronized yet + No timetable + for week %s + Contact your tutor in order to get the timetable. + Timetable is not public + No timetable + Choose date + Syncing timetable for the chosen week… + Today Sync all Debugging Feedback @@ -794,16 +1188,22 @@ Profile manager Switch the semester Changing profile… + today + tomorrow + Syncing… Unknown Update needed You have to update the app in order to use it. Click OK to download. You\'re trying to run an older version of the app which may break its data. Paired browsers No connection. + Paired %s No paired browsers. Pair a browser Do you want to remove this browser? + Token is invalid You can\'t do this + Unpair You have to enable registration in order to use Notification forwarding.\n\nSelect \"Allow registration\" in E-register Settings. What do you want to do? What is this? @@ -819,54 +1219,18 @@ Synchronise Use a bigger font No lessons on selected day. + No lessons for the next 7 days + Timetable isn\'t synchronized. Open the app and synchronize the timetable. The profile does not exist.\nAdd the widget again. + No lessons for the next 7 days. + Timetable isn\'t synchronized. Timetable Unified timetable Yes yesterday You\'re offline. Try enabling Wi-Fi or mobile data. Internet connection - Current - Past - There is no homework. - By date - By subject - LIBRUS account has lost connection with the Synergia account. Login on portal.librus.pl or in the Librus mobile app and follow the instructions to fix the problem. - Compose - Add a new profile - Add or remove students\' profiles - Wrong module given: %d Report it to the application developer - Add event - Save own event or homework in the agenda - Change Agenda view - Debugging - Help and FAQ - Save the timetable as an image - Generates timetable for the whole week - End-of-year average calculation method - Grade averages - Show semester and end-of-year averages - Grades color - Sort grades - Message - Set your register number - Add an attachment - Abort message - Save draft - Send - Absent teachers - Getting grade comments - Getting school free days - Show teacher absences in Agenda - Mark everything as read - App language - "English " - Change app language - Notice. This feature may not work on some devices or in some parts of the app. - Discord server - Join our Discord community! - Use system language - (child) - (parent) - Syncing... + In order to download the file, you have to grant file storage permission for the application.\n\nClick OK to grant the permission. + You denied the required permissions for the application.\n\nIn order to grant the permission, open the Permissions screen for Szkolny.eu in phone settings.\n\nClick OK to open app settings now. + Required permissions diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 2fbb3d4e..dc9d55f7 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -8,4 +8,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5d04d97c..a5e975f1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -13,6 +13,9 @@ #3e7f7f7f + #2196f3 + #64b5f6 + #ffffffff diff --git a/app/src/main/res/values/errors.xml b/app/src/main/res/values/errors.xml index e6718985..3e5f48d6 100644 --- a/app/src/main/res/values/errors.xml +++ b/app/src/main/res/values/errors.xml @@ -4,6 +4,9 @@ --> + ERROR_APP_CRASH + ERROR_MESSAGE_NOT_SENT + ERROR_REQUEST_FAILURE ERROR_REQUEST_HTTP_400 ERROR_REQUEST_HTTP_401 @@ -11,17 +14,28 @@ ERROR_REQUEST_HTTP_404 ERROR_REQUEST_HTTP_405 ERROR_REQUEST_HTTP_410 - ERROR_REQUEST_HTTP_500 + ERROR_REQUEST_HTTP_424 + ERROR_REQUEST_HTTP_500 + ERROR_REQUEST_HTTP_503 + ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND + ERROR_REQUEST_FAILURE_TIMEOUT + ERROR_REQUEST_FAILURE_NO_INTERNET + ERROR_REQUEST_FAILURE_SSL_ERROR ERROR_RESPONSE_EMPTY ERROR_LOGIN_DATA_MISSING ERROR_LOGIN_DATA_INVALID ERROR_PROFILE_MISSING + ERROR_PROFILE_ARCHIVED ERROR_INVALID_LOGIN_MODE ERROR_LOGIN_METHOD_NOT_SATISFIED ERROR_NOT_IMPLEMENTED + ERROR_FILE_DOWNLOAD ERROR_NO_STUDENTS_IN_ACCOUNT + ERROR_CAPTCHA_NEEDED + ERROR_CAPTCHA_LIBRUS_PORTAL + CODE_INTERNAL_LIBRUS_ACCOUNT_410 CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED @@ -78,6 +92,14 @@ ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN ERROR_LIBRUS_API_MAINTENANCE + ERROR_LIBRUS_PORTAL_MAINTENANCE + ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM + ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED + ERROR_LIBRUS_API_DEVICE_REGISTERED + ERROR_LIBRUS_MESSAGES_NOT_FOUND + ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST + ERROR_LIBRUS_MESSAGES_ATTACHMENT_NOT_FOUND + ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD @@ -92,6 +114,7 @@ ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID + ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM ERROR_LOGIN_VULCAN_INVALID_SYMBOL ERROR_LOGIN_VULCAN_INVALID_TOKEN @@ -106,6 +129,7 @@ ERROR_VULCAN_API_MAINTENANCE ERROR_VULCAN_API_BAD_REQUEST ERROR_VULCAN_API_OTHER + ERROR_VULCAN_ATTACHMENT_DOWNLOAD ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME @@ -126,6 +150,21 @@ ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA ERROR_IDZIENNIK_API_ACCESS_DENIED ERROR_IDZIENNIK_API_OTHER + ERROR_IDZIENNIK_API_NO_REGISTER + ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION + + ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN + ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER + ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID + ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS + ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED + ERROR_EDUDZIENNIK_WEB_TEAM_MISSING + + ERROR_LOGIN_PODLASIE_API_INVALID_TOKEN + ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT + ERROR_PODLASIE_API_NO_TOKEN + ERROR_PODLASIE_API_OTHER + ERROR_PODLASIE_API_DATA_MISSING ERROR_TEMPLATE_WEB_OTHER @@ -137,32 +176,54 @@ EXCEPTION_LIBRUS_SYNERGIA_REQUEST EXCEPTION_MOBIDZIENNIK_WEB_REQUEST EXCEPTION_VULCAN_API_REQUEST - EXCEPTION_NOTIFY_AND_SYNC + EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST + EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST + EXCEPTION_NOTIFY EXCEPTION_LIBRUS_MESSAGES_REQUEST EXCEPTION_IDZIENNIK_WEB_REQUEST EXCEPTION_IDZIENNIK_WEB_API_REQUEST EXCEPTION_IDZIENNIK_API_REQUEST + EXCEPTION_EDUDZIENNIK_WEB_REQUEST + EXCEPTION_EDUDZIENNIK_FILE_REQUEST + ERROR_ONEDRIVE_DOWNLOAD + EXCEPTION_VULCAN_WEB_LOGIN + EXCEPTION_VULCAN_WEB_REQUEST + EXCEPTION_PODLASIE_API_REQUEST LOGIN_NO_ARGUMENTS - Błąd odpowiedzi serwera - Błąd serwera: nieprawidłowe zapytanie + Aplikacja przestała działać + Nie udało się wysłać wiadomości: nowa wiadomość nie została odnaleziona na liście wiadomości wysłanych + + Błąd odpowiedzi serwera. Dziennik może być przeciążony lub mieć przerwę techniczną. + Błąd serwera: nieprawidłowe zapytanie. Dziennik może być przeciążony lub mieć przerwę techniczną. Błąd serwera: odmowa dostępu Błąd serwera: dostęp zabroniony Błąd serwera: plik nie znaleziony - Błąd serwera: nieprawidłowa metoda zapytania - Błąd serwera: odpowiedź niedostępna - Wewnętrzny błąd serwera - Brak odpowiedzi serwera - Dane logowania niekompletne + Błąd serwera: nieprawidłowa metoda zapytania. Dziennik może być przeciążony lub mieć przerwę techniczną. + Błąd serwera: odpowiedź niedostępna. Dziennik może być przeciążony lub mieć przerwę techniczną. + Błąd serwera: niespełnione zależności. Dziennik może być przeciążony lub mieć przerwę techniczną. + Wewnętrzny błąd serwera. Dziennik może być przeciążony lub mieć przerwę techniczną. + Dziennik jest tymczasowo niedostępny + Brak internetu: nie znaleziono adresu serwera + Przekroczono czas oczekiwania. Dziennik może być przeciążony lub mieć przerwę techniczną. + Brak internetu + Połączenie bezpieczne nie powiodło się. Sprawdź, czy masz ustawioną poprawnie datę i godzinę. + Brak odpowiedzi serwera. Dziennik może być przeciążony lub mieć przerwę techniczną. + Dane logowania niekompletne. Skontaktuj się z twórcą aplikacji. Nieprawidłowe dane logowania Profil nie został ustawiony - Nieprawidłowy sposób logowania - Nie można wywołać metody logowania + Profil jest archiwalny - synchronizacja profilu z poprzedniego roku szkolnego nie jest możliwa + Nieprawidłowy sposób logowania. Skontaktuj się z twórcą aplikacji. + Nie można wywołać metody logowania. Skontaktuj się z twórcą aplikacji. Nie zaimplementowano + Wystąpił błąd podczas pobierania pliku. Dziennik może być przeciążony lub mieć przerwę techniczną. Brak uczniów przypisanych do konta + Wymagane rozwiązanie zadania Captcha + LIBRUS®️: wymagane rozwiązanie zadania Captcha + CODE_INTERNAL_LIBRUS_ACCOUNT_410 CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED Wymagane wypełnienie CAPTCHA @@ -171,7 +232,7 @@ Wymagana akceptacja regulaminu Błąd zmiany hasła Wymagana zmiana hasła - Librus API: nieprawidłowe dane logowania + LIBRUS® API: nieprawidłowe dane logowania Inny błąd logowania do API Brak tokenu CSRF Konto LIBRUS nie zostało aktywowane @@ -195,12 +256,12 @@ Brak dostępu do Wiadomości Brak dostępu do Synergii Brak ID sesji Wiadomości - Odmowa dostępu do Portalu Librus - API Portalu Librus wyłączone - Konto Synergia zostało rozłączone - Inny błąd Portalu Librus - Nie znaleziono konta Synergia - Inny błąd logowania do Portalu Librus + Odmowa dostępu do Portalu LIBRUS® + API Portalu LIBRUS® wyłączone + Konto LIBRUS® utraciło połączenie z kontem Synergia. Zaloguj się na stronie portal.librus.pl lub w oficjalnej aplikacji LIBRUS® i postępuj zgodnie z instrukcją, aby naprawić konto. + Inny błąd Portalu LIBRUS® + Nie znaleziono konta Synergia. Zaloguj się na stronie portal.librus.pl, a następnie powiąż swoje konto Synergia do konta LIBRUS® Portal. + Inny błąd logowania do Portalu LIBRUS® ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID @@ -208,81 +269,114 @@ ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT - ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID + Błędne ID klienta. Zgłoś błąd programiście oraz zaktualizuj aplikację. ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED ERROR_LIBRUS_SYNERGIA_OTHER - Librus Synergia: przerwa techniczna - Librus Wiadomości: przerwa techniczna - ERROR_LIBRUS_MESSAGES_ERROR - ERROR_LIBRUS_MESSAGES_OTHER - Librus Wiadomości: nieprawidłowe dane logowania - Librus Portal: nieprawidłowe dane logowania - Librus API: przerwa techniczna + LIBRUS® Synergia: przerwa techniczna + LIBRUS® Wiadomości: przerwa techniczna + LIBRUS® Wiadomości: serwer zwrócił błąd. Prześlij zgłoszenie błędu. + LIBRUS® Wiadomości: serwer zwrócił nieznany błąd. Prześlij zgłoszenie błędu. + LIBRUS® Wiadomości: nieprawidłowe dane logowania + LIBRUS® Portal: nieprawidłowe dane logowania + LIBRUS® API: przerwa techniczna + LIBRUS® Portal: przerwa techniczna + Wystąpił problem z tablicą ogłoszeń + LIBRUS®: Sesja logowania wygasła. Zaloguj się ponownie. + Urządzenie jest już zarejestrowane + Nie znaleziono wiadomości. Mogła zostać usunięta. + Nieprawidłowe dane dostępu. Sprawdź poprawność wprowadzonych danych. + Nie znaleziono załącznika. Mógł zostać usunięty. + Logowanie LIBRUS® Wiadomości: ReCaptcha przekroczono czas oczekiwania. - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN - ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_DEVICE - ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED - ERROR_LOGIN_MOBIDZIENNIK_WEB_MAINTENANCE - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS - ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER - ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED - ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY - ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE - ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID - ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE - ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID + Nieprawidłowy login lub hasło + Podano stare hasło + Nieprawidłowe urządzenie - zgłoś błąd programiście + Dziennik jest archiwalny - logowanie nie jest możliwe + Chwilowa przerwa techniczna MobiDziennika, lub inny problem z serwerem + Nieprawidłowy adres szkoły + Nieznany błąd logowania do MobiDziennika + MobiDziennik: brak dostępu + MobiDziennik: brak klucza sesji + MobiDziennik: brak wartości sesji + MobiDziennik: brak identyfikatora serwera + MobiDziennik: błąd odpowiedzi serwera + Brak identyfikatora sesji przy logowaniu + MobiDziennik: problemy z wydajnością serwerów. Spróbuj ponownie później. - ERROR_LOGIN_VULCAN_INVALID_SYMBOL - ERROR_LOGIN_VULCAN_INVALID_TOKEN - ERROR_LOGIN_VULCAN_INVALID_PIN - ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING - ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING - ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING - ERROR_LOGIN_VULCAN_EXPIRED_TOKEN - ERROR_LOGIN_VULCAN_OTHER - ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN - ERROR_LOGIN_VULCAN_NO_PUPILS - ERROR_VULCAN_API_MAINTENANCE - ERROR_VULCAN_API_BAD_REQUEST - ERROR_VULCAN_API_OTHER + Nieprawidłowy symbol + Nieprawidłowy token + Nieprawidłowy PIN + Nieprawidłowy PIN: pozostało 0 prób + Nieprawidłowy PIN: pozostała 1 próba + Nieprawidłowy PIN: pozostały 2 próby + Token wygasły, wygeneruj ponownie + Inny błąd logowania do dziennika VULCAN® + Dziennik przedszkolny - logowanie niemożliwe + Brak uczniów przypisanych do konta, bądź ukończyli oni już szkołę + VULCAN®: przerwa techniczna + VULCAN®: błąd żądania, zgłoś błąd + VULCAN®: inny błąd, wyślij zgłoszenie + VULCAN®: nie znaleziono adresu załącznika - ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN - ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME - ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED - ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE - ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR - ERROR_LOGIN_IDZIENNIK_WEB_OTHER - ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS - ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION - ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH - ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER - ERROR_IDZIENNIK_WEB_ACCESS_DENIED - ERROR_IDZIENNIK_WEB_OTHER - ERROR_IDZIENNIK_WEB_MAINTENANCE - ERROR_IDZIENNIK_WEB_SERVER_ERROR - ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED - ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR - ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA - ERROR_IDZIENNIK_API_ACCESS_DENIED - ERROR_IDZIENNIK_API_OTHER + Nieprawidłowe dane logowania + Nieprawidłowa nazwa szkoły + iDziennik: wymagana zmiana hasła. Zaloguj się na stronie iDziennika, by dokonać zmiany hasła. + Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem + Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem (serwer zwrócił błąd) + iDziennik: inny błąd logowania + iDziennik: brak dostępu do API + iDziennik: nie znaleziono sesji + iDziennik: nie znaleziono tokenu autoryzacji + iDziennik: nie znaleziono tokenu API + iDziennik: brak dostępu. Zaloguj się ponownie. + iDziennik: inny błąd synchronizacji + Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem + Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem (serwer zwrócił błąd) + iDziennik: wymagana zmiana hasła. Zaloguj się na stronie iDziennika, by dokonać zmiany hasła. + iDziennik: nie znaleziono numeru roku szkolnego. Uczeń może nie być aktywny w aktualnym roku szkolnym. + iDziennik: serwer nie zwrócił żadnych danych + Brak dostępu do API iDziennika + Inny błąd API iDziennika + iDziennik: nie znaleziono ID dziennika. Zgłoś błąd programiście. + Twoje konto nie ma uprawnień do wysyłania wiadomości (bądź wystąpił inny błąd serwera iDziennika) + + Błędny email lub hasło + Inny błąd logowania + Brak ID sesji + Ograniczony dostęp do dziennika + Sesja wygasła + Nie można pobrać informacji o klasie i szkole + + Nieprawidłowy token + Przekroczono maksymalną liczbę urządzeń (5) + ERROR_PODLASIE_API_NO_TOKEN + ERROR_PODLASIE_API_OTHER + Brak danych. Zgłoś błąd programiście. ERROR_TEMPLATE_WEB_OTHER - EXCEPTION_API_TASK + Błąd synchronizacji. Upewnij się, że masz połączenie z internetem, a następnie zgłoś błąd. EXCEPTION_LOGIN_LIBRUS_API_TOKEN EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN - EXCEPTION_LIBRUS_API_REQUEST + Zgłoś błąd: wyjątek w API LIBRUS® EXCEPTION_LIBRUS_SYNERGIA_REQUEST EXCEPTION_MOBIDZIENNIK_WEB_REQUEST EXCEPTION_VULCAN_API_REQUEST - EXCEPTION_NOTIFY_AND_SYNC - EXCEPTION_LIBRUS_MESSAGES_REQUEST + EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST + EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST + EXCEPTION_NOTIFY + Zgłoś błąd: pobieranie wiadomości LIBRUS® EXCEPTION_IDZIENNIK_WEB_REQUEST EXCEPTION_IDZIENNIK_WEB_API_REQUEST EXCEPTION_IDZIENNIK_API_REQUEST + Wystąpił błąd + Wystąpił błąd podczas pobierania pliku + Nie udało się pobrać pliku z OneDrive + EXCEPTION_VULCAN_WEB_LOGIN + EXCEPTION_VULCAN_WEB_REQUEST + Zgłoś błąd: wyjątek w API PPE Nie podano parametrów - \ No newline at end of file + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..d75968c4 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,8 @@ + + + + + #0C92D6 + diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..fd9da7a5 --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 8e7275b2..bb74ba85 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -81,4 +81,82 @@ %d błędów %d błędów - \ No newline at end of file + + + Szkolny.eu: %d nowe powiadomienie + Szkolny.eu: %d nowe powiadomienia + Szkolny.eu: %d nowych powiadomień + + + %d nowe powiadomienie + %d nowe powiadomienia + %d nowych powiadomień + + + + %d zmiana planu lekcji + %d zmiany planu lekcji + %d zmian planu lekcji + + + %d nowa ocena + %d nowe oceny + %d nowych ocen + + + %d nowe wydarzenie + %d nowe wydarzenia + %d nowych wydarzeń + + + %d nowe zadanie domowe + %d nowe zadania domowe + %d nowych zadań domowych + + + %d udostępnione wydarzenie + %d udostępnione wydarzenia + %d udostępnionych wydarzeń + + + %d udostępnione zadanie domowe + %d udostępnione zadania domowe + %d udostępnionych zadań domowych + + + %d nowa wiadomość + %d nowe wiadomości + %d nowych wiadomości + + + %d nowa uwaga + %d nowe uwagi + %d nowych uwag + + + %d nowy wpis frekwencji + %d nowe wpisy frekwencji + %d nowych wpisów frekwencji + + + szczęśliwy numerek + %d szczęśliwe numerki + %d szczęśliwych numerków + + + %d nowe ogłoszenie + %d nowe ogłoszenia + %d nowych ogłoszeń + + + %d inne powiadomienie + %d inne powiadomienia + %d innych powiadomień + + + + %d ocena + %d oceny + %d ocen + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95ff61c1..f7536aaf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,20 +1,22 @@ + Semestr 1 Semestr 2 - - - Od najnowszych - Wg nazwy przedmiotu - - Wg e-dziennika Wg wartości oceny - + + Od najnowszych + Wg nazwy przedmiotu + + + Wydarzenie + Zadanie domowe + styczeń luty @@ -29,65 +31,80 @@ listopad grudzień - - - Wydarzenie - Zadanie domowe + + stycznia + lutego + marca + kwietnia + maja + czerwca + lipca + sierpnia + września + października + listopada + grudnia - Szkolny.eu - Szkolny.eu | %s - - Jasny - Ciemny - Czarny / OLED - Czekoladowy - Indygo - Jasny żółty - Ciemny niebieski - Niebieski - Jasny niebieski - Ciemny fioletowy - Fioletowy - Jasny fioletowy - Ciemny czerwony - Czerwony - Jasny czerwony - Ciemny zielony - Bursztynowy - Jasny zielony - Przerwij Dodaj Zmiany planu lekcji + Nieobecni nauczyciele %s temu + Na urządzeniu prawdopodobnie zainstalowany jest menedżer aplikacji, który może powodować problemy z synchronizacją automatyczną.\n\nNależy wyłączyć w ustawieniach telefonu optymalizację baterii dla aplikacji Szkolny.eu.\n\nKliknij OK, aby przejść do ustawień telefonu. + Wykryto problem z synchronizacją + Nie udało się otworzyć ustawień + Szkolny.eu + Szkolny.eu | %s Czy na pewno? nb u sp su + Grupuj kolejne dni na liście + Wyświetlaj obecność w widoku miesięcy + Konfiguracja frekwencji + Używaj symboli i kolorów wg dziennika + Widoczne po rozwinięciu listy + Nie ma tutaj żadnych nieobecności. + w + lekcja %d + %.2f%% + Obecność w tym okresie: %.2f%% ob zw + Podsumowanie - semestr %d  + Podsumowanie - cały rok  + Dni + Lista + Miesiące + Podsumowanie Nieobecności: W tym nieusprawiedliwione: Spóźnienia: - Brak nieobecności. + Nie masz żadnych nieobecności. Obecności: Zwolnienia: Wszystkie przedmioty Podsumowanie - ładowanie… - Podsumowanie - semestr %d  - Podsumowanie - cały rok  Wróć Dostosuj wartość przesunięcia dzwonka. Format: ±H:MM:SS Nieprawidłowy format + przerwa (%s) Synchronizacja jest niemożliwa, ponieważ teraz nie ma żadnych lekcji. Spróbuj jeszcze raz np. pod koniec lekcji albo przerwy. Pamiętaj, że powinieneś to uruchomić jeszcze przed planowanym czasem dzwonka. + Wybierz najbliższy dzwonek, abyś mógł go zsynchronizować z aplikacją. "\n\nAktualna kalibracja to %s" - Kliknij OK, kiedy lekcja się skończy. Licznik czasu zostanie zsynchronizowany z czasem dzwonka.\n\nCzas według planu to %s + Kliknij w ikonę dzwonka, kiedy dzwonek zadzwoni. Licznik czasu zostanie zsynchronizowany z czasem dzwonka.\n\nCzas według planu to %s\n\nAktualna różnica wynosi %s + lekcja %s (%s) Czy na pewno zresetować synchronizację? - Dzwonek jest niedokładny o %s%s + Dzwonek jest niedokładny o %s + Godzina dzwonka do synchronizacji Synchronizacja z dzwonkiem + Nie znaleziono aplikacji kalendarza Anuluj + Przesuń w dół + Przesuń w górę + Najbliższe wydarzenia Przejdź do ocen Oceny - ostatnie 7 dni Brak ocen do wyświetlenia @@ -111,15 +128,23 @@ Plan lekcji na %s Koniec lekcji! To nie powinno się nigdy pokazać! Zgłoś błąd! + Najbliższe wydarzenia + Ostatnie oceny + Szczęśliwy numerek + Plan lekcji Pobierz Posiadasz wersję %s, dostępna jest %s. Dostępna jest aktualizacja Wybierz plik Wybierz profil Zamknij + Napisz Napisz + %s%s + Konfiguruj Skopiowano do schowka Skopiuj do schowka + Synchronizuj Jeśli aplikacja nie chce się uruchomić, możesz spróbować ponownie pobrać dane. Kliknij na przycisk, a następnie Restartuj aplikację. Szczegóły błędu Wiadomość od dewelopera @@ -137,6 +162,8 @@ dzisiaj (%s) jutro (%s) Generalnie nie przejmuj się tym co tu widzisz. + Te ustawienia nie są przeznaczone dla zwykłych użytkowników, wyłącznie dla twórcy tej aplikacji.\n\nNie są nawet w żaden sposób opisane, nie wiadomo co robią, więc możesz nawet nie wiedzieć kiedy coś zepsujesz.\n\nWłączenie tych opcji może spowodować utratę danych w aplikacji, uszkodzenie twojego systemu lub nawet uruchomienie wirusa na baterii.\n\nLepiej uważaj. + Developer mode Przewidywana średnia na semestr %d:\n%#.2f\n Przewidywana średnia roczna:\n%#.2f\n Średnia ocen końcowych na semestr %d:\n%#.2f\n @@ -147,6 +174,17 @@ Średnie ocen Średnie ocen na semestr %d nie są dostępne Roczna średnia ocen nie jest dostępna + %s, %s + %s - %s (%s lekcji - %s godzin %s minut) + Brak wydarzeń tego dnia. + Szczegóły błędu + Dodano + Załączniki + Treść + Przedmiot + Nauczyciel + Grupa + Temat Brak wydarzeń w wybranym okresie. Cały dzień Aby udostępnić wydarzenie, musisz wybrać grupę (klasę) odbiorców. @@ -154,14 +192,24 @@ Wybierz lekcję -- inna godzina -- Data + Wybierz datę Termin + nast. lekcja %s + przyszły %s (%s) + -- inna data -- + %s (%s) + dzisiaj (%s) + jutro (%s) Lekcja Wybierz lekcję + Więcej opcji Aby móc coś udostępnić, musisz wyrazić zgodę na rejestrację.\nSpokojnie, rejestracja jest automatyczna, więc nie musisz podawać żadnych danych.\n\nKliknij Więcej, aby dowiedzieć się o rodzaju przetwarzanych danych. Nie jesteś zarejestrowany + Nie ma lekcji tego dnia Bez przedmiotu Bez nauczyciela Brak grupy + Nie pobrano planu lekcji… To wydarzenie zostało udostępnione przez inną osobę. Usuniesz je tylko u siebie, ale już nigdy nie otrzymasz go ponownie. To wydarzenie zostało przez ciebie udostępnione, dlatego zostanie usunięte również u wszystkich osób z grupy. Udostępnij klasie @@ -175,6 +223,9 @@ Nauczyciel Grupa Wybierz grupę + Lekcja/godzina + Wybierz godzinę + Dodaj wpis do terminarza Temat Podaj temat Rodzaj @@ -188,7 +239,7 @@ Opis Historia oceny ID oceny - Ocena jest ukryta - została poprawiona + Ocena została poprawiona (brak kategorii) (brak opisu) semestr %d @@ -196,17 +247,36 @@ Wartość do średniej waga %.2f Kolor ocen + Wg wartości oceny + Wg e-dziennika + Od najnowszych + Wg nazwy przedmiotu Sortuj oceny Szczegóły + Sala lekcyjna + ID lekcji + Nr lekcji + Lekcja przeniesiona z %s + Lekcja przeniesiona na %s + Nauczyciel + Grupa + Przejdź + Brak wydarzeń na tej lekcji. + Możesz wpisać wydarzenie ręcznie, używając przycisku Dodaj. + Zaznacz, które powiadomienia mają być pokazywane w systemie oraz w aplikacji. + Pokazuj wybrane powiadomienia + Profil został usunięty. Na pewno usunąć wybrane wydarzenie? Własne wydarzenie Własne zadanie domowe + Synchronizacja ręczna Profil Duża czcionka Przezroczystość Motyw Nic nie rób Gotowe + Nie pytaj ponownie Pobrać plik %s (%s)? Pobieram… Pobieranie pliku @@ -218,9 +288,76 @@ Zaloguj się na nowy profil Eksportuj dane aplikacji Dodaj lub usuń profile uczniów + Nie ma więcej lekcji tego przedmiotu. Pobierz plan lekcji i spróbuj ponownie. + Własny przedmiot + Anuluj + Zamknij + Zgłaszanie błędów… + Zgłaszanie błędów + Synchronizacja przerwana + Pierwsze logowanie + Tworzenie powiadomień + Trwa synchronizacja profilu %s… + Dzięki niej, aplikacja Szkolny.eu może synchronizować dane z e-dziennikiem. Możesz ją zamknąć, ponieważ w tej chwili nic nie robi. + Usługa synchronizacji + Pobieranie szczegółów konta… + Pobieranie ogłoszeń szkolnych… + Pobieranie frekwencji ucznia… + Pobieranie kategorii obecności… + Pobieranie uwag ucznia… + Pobieranie ocen z zachowania… + Pobieranie kalendarza… + Pobieranie informacji o klasie… + Pobieranie listy sal lekcyjnych… + Pobieranie danych… + Pobieranie ocen opisowych… + Pobieranie słowników… + Pobieranie kategorii wydarzeń… + Pobieranie wydarzeń kalendarza… + Pobieranie listy sprawdzianów… + Pobieranie kategorii ocen… + Pobieranie komentarzy ocen… + Pobieranie ocen ucznia… + Pobieranie zadań domowych… + Pobieranie listy lekcji… + Pobieranie szczęśliwego numerka… + Pobieranie wiadomości… + Pobieranie wiadomości odebranych… + Pobieranie wiadomości wysłanych… + Pobieranie kategorii uwag… + Pobieranie uwag… + Pobieranie ocen punktowych… + Pobieranie ocen proponowanych… + Pobieranie zebrań z rodzicami… + Konfigurowanie natychmiastowych powiadomień… + Pobieranie informacji o szkole… + Pobieranie informacji o uczniu… + Pobieranie listy przedmiotów… + Pobieranie rodzajów nieobecności nauczycieli… + Pobieranie nieobecności nauczycieli… + Pobieranie listy nauczycieli… + Pobieranie grup klasowych… + Pobieranie planu lekcji… + Pobieranie informacji o jednostce… + Logowanie do Edudziennika… + Logowanie do iDziennika… + Logowanie do iDziennika… + Logowanie do API + Logowanie do wiadomości LIBRUS® + Logowanie do Portalu LIBRUS® Rodzina + Logowanie do LIBRUS® Synergia + Logowanie do API MobiDziennika… + Logowanie do API MobiDziennika… + Logowanie do MobiDziennika… + Logowanie do Template API… + Logowanie do Template WEB… + Logowanie do VULCAN® API… + Synchronizowanie udostępnionych wydarzeń… + Tworzenie powiadomień… Błąd Szczegóły błędu + Podano nieprawidłowy moduł: %d Zgłoś błąd programiście Twoje hasło do iDziennika wygasło, co oznacza że musisz je zmienić w wersji komputerowej serwisu. Identyfikator przedmiotu nie został podany. Wystąpił błąd! @@ -231,22 +368,45 @@ Nie znaleziono roku szkolnego. Prawdopodobnie uczeń nie jest przypisany do dziennika w tym semestrze. Opcja edycji wydarzeń innych uczniów nie została jeszcze zaimplementowana. Nie znaleziono ID ucznia. Prawdopodobnie aplikacja została zaktualizowana.\n\nWykonaj ponownie synchronizację lub spróbuj ponownie za 5 minut. + Kod błędu: %d (w %s) Dodano: cały dzień wydarzenie klasowe + %1$s przez %2$s + %1$s przez Ciebie + %1$s + {cmd-share-variant} %1$s przez %2$s + {cmd-share-variant} %1$s przez Ciebie wypracowanie sprawdzian wycieczka informacja + Dodano %1$s przez %2$s%3$s + Dodano %1$s przez Ciebie%3$s + Dodano %1$s%3$s + {cmd-share-variant} %1$s przez %2$s%3$s + {cmd-share-variant} %1$s przez Ciebie%3$s + Aby móc udostępnić wydarzenie, należy włączyć opcję rejestracji na serwerze. Pozwala to na tworzenie i odbieranie wydarzeń udostępnionych w Twojej klasie.\n\nPo kliknięciu OK zostanie ona automatycznie włączona.\n\nUpewnij się, że zapoznałeś się z warunkami i akceptujesz jej postanowienia. + Udostępnianie wydarzeń + Usuwam wydarzenie… + Zapisuję wydarzenie… + Udostępniam wydarzenie… + Usuwam wydarzenie u reszty klasy… + Usuwam udostępnione wydarzenie… + Czy chcesz oznaczyć to zadanie jako wykonane?\n\nNie będzie ono się wyświetlać na stronie głównej oraz w aktualnych zadaniach domowych. Będzie wciąż dostępne w Terminarzu. + Oznacz jako wykonane inny projekt zebranie z rodzicami lektura + Usuwanie udostępnionego wydarzenia… {cmd-share-variant} przez %s ciebie Udostępnianie wydarzeń + Udostępnianie wydarzenia… kartkówka zadanie domowe + Nie ma więcej wydarzeń w kalendarzu. Wyjdź Często zadawane pytania Czy znalazłeś odpowiedź na Twoje pytanie? Jeśli nie, możesz zadać pytanie bezpośrednio do mnie. @@ -265,6 +425,7 @@ %s - proponowana Punkty startowe Punkty startowe: semestr %d + z %s %s (roczna) %s - roczna %s (prop. roczna) @@ -272,9 +433,26 @@ %s, %s %s%%, %s%% %spkt, %spkt - %s - %s%% + brak średniej + średnia: %s + punkty: %s + sem. %1$d: %2$s + %s + %s%% %spkt + suma: %s + %3$s%% + %s pkt + roczna: %2$s + Licz średnią jeśli wszystkie wagi to 0 + Pozwala na liczenie średniej arytmetycznej z przedmiotów, w których wszystkie wystawione oceny mają wagę 0 (nie są liczone do średniej).\n\nJeśli taki przedmiot celowo nie powinien być liczony, odznacz okienko przy tym ustawieniu. + Wyklucz wybrane oceny ze średniej + Oceny oddziel przecinkiem + Podaj oceny… + Ukrywaj oceny poprawione z listy + Własna wartość minusa + Własna wartość plusa + Konfiguracja ocen Dodaj ocenę Dodawanie oceny Podaj wagę oceny @@ -289,44 +467,117 @@ Twoja średnia na koniec roku: waga %s inna waga + Nie ma ocen w tym semestrze. (poprawa) %s max %s pkt - Brak przedmiotów do wyświetlenia + Brak ocen w dzienniku. + Semestr %d Semestr 1 Semestr 2 semestr %d: %s semestr %d: %s%% semestr %d: %spkt + Semestr %d Semestr %d + Aktualne ustawienia ocen mogą wpływać na średnią. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj. + Została ustawiona własna wartość plusa/minusa. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj. + *średnie ocen są poglądowe i mogą się różnić, w zależności od ustawień szkoły + *przewidywana średnia + *z ocen końcowych\nPrzewidywana: %s + *z ocen końcowych + *z ocen proponowanych\nPrzewidywana: %s + *z ocen proponowanych + Ocena przewidywana z danego przedmiotu jest obliczana na podstawie aktualnej średniej ważonej.\n\nOcena jest liczbą całkowitą, jaką wystawiłby nauczyciel bazując na średniej. Liczba zaokrąglona jest w górę jeśli część po przecinku przekroczy ,75.\nPrzykładowo: średnie 3,75 jak również 4,74 dają w wyniku ocenę dobrą (4).\n\nŚrednia przewidywana ze wszystkich przedmiotów obejmuje obliczone w ten sposób oceny końcowe. + Obliczanie średniej wszystkich przedmiotów + Zbyt mało danych do wyliczenia średniej. + Średnia zwykłych ocen + Średnia przedmiotów punktowych + Śr. ocen proponowanych:\n%s + semestr 1 + semestr 2 + Statystyka ocen + całoroczna wartość: %s waga %s nie liczona do śr. koniec roku: %s koniec roku: %s%% koniec roku: %spkt + Cały rok: %s • %s Witaj pusty fragmencie Pomoc Przekazywanie powiadomień umożliwia sparowanie z przeglądarką internetową w celu wysyłania wszystkich powiadomień z aplikacji na komputer. Można do nich zaliczyć np. nowe oceny, wydarzenia, zadania domowe itp.\n\nKliknij \"Przekazywanie powiadomień\", aby rozpocząć. Rejestracja odbędzie się automatycznie przy pierwszym logowaniu do e-dziennika.\n\nDo poprawnego działania funkcji zostaną przesłane:\n- Twój identyfikator szkoły i klasy\n- Twoja nazwa użytkownika do e-dziennika\n- Twoje imię i nazwisko\n\nJedyne dane widoczne dla innych osób z twojej klasy to Imię i Nazwisko podczas udostępniania wydarzeń. Nie zostaną wysłane żadne prywatne dane, takie jak hasła, oceny itp. Dowiedz się więcej w Polityce Prywatności. + Pobierz ponownie + Edytuj wydarzenie + Idź do planu lekcji + Oznacz jako wykonane + Zapisz do kalendarza + Dodaj/usuń karty + Możesz usunąć karty przesuwając w lewo lub zmienić ich kolejność, przytrzymując na kartę. + %s • Numer w dzienniku to %d + %s • Kliknij, aby ustawić swój numerek. + Dnia %s szczęśliwy numerek to %d. + Brak informacji o szczęśliwym numerku. + Nie ma dzisiaj szczęśliwego numerka. + %d to dzisiejszy szczęśliwy numerek. + %d to szczęśliwy numerek na jutro. + Dnia %s Twój numerek jest szczęśliwy. + Dzisiaj to Ty masz szczęśliwy numerek! + Jutro to Ty masz szczęśliwy numerek! + %1$s, %2$s + %1$s, %2$s + Później: + brak lekcji + Pierwsza: %s + Za chwilę: %s + Teraz: %s + %d lekcji - %s do %s + Pozostało lekcji: %d - do %s + Nie ma żadnych lekcji + Przez następne 7 dni nie ma żadnych lekcji. + Nie pobrano planu lekcji + Pobierz + Plan lekcji na tydzień %s nie został jeszcze pobrany. + Brak planu lekcji + Plan lekcji nie został opublikowany przez szkołę.\n\nSkontaktuj się z wychowawcą. + Dzisiaj + Jutro (%1$s) Edytuj + Brak zadań domowych. %s, %s + Aktualne + Minione %s %s Zgadzam się Nie zgadzam się + za %s Nieprawidłowy format + English + Deutsch + Polski + Według systemu Nowa lekcja Przerwa Lekcja odwołana Zastępstwo + Lekcja przeniesiona Zmiana planu + Nie ma dzisiaj więcej lekcji! Ładowanie… + To konto nie ma żadnych uczniów + Do tego konta nie ma przypisanego żadnego ucznia, dlatego zalogowanie nie jest możliwe.\n\nPrzypisz ucznia na stronie swojego e-dziennika lub zaloguj się kontem, które ma przypisanego ucznia. Zezwól na rejestrację Zaloguj Czy na pewno chcesz anulować logowanie w aplikacji? Wyświetlane profile nie zostaną zapisane. + Użyj danych, którymi logujesz się do wersji komputerowej Edudziennika. + Zaloguj się - Edudziennik Błąd: %s Konto nie zostało aktywowane Token wygasł. Wygeneruj ponownie Nieprawidłowa nazwa serwera + Nieprawidłowy kod + Nieprawidłowy kod lub PIN Nieprawidłowy e-mail Nieprawidłowy login Nieprawidłowy login lub hasło @@ -335,9 +586,11 @@ Nieprawidłowa nazwa szkoły Nieprawidłowy symbol szkoły Nieprawidłowy token + Nieprawidłowy token lub PIN Nieprawidłowa nazwa użytkownika Podaj adres serwera nie podano argumentów + Podaj kod Podaj e-mail Podaj login Podaj hasło @@ -368,10 +621,13 @@ Pomoc - iDziennik Progman Zaloguj się danymi, które otrzymałeś od swojej szkoły. W razie problemów skorzystaj z przycisku pod formularzem. Zaloguj się - iDziennik Progman - Aby korzystać z aplikacji musisz posiadać konto Librus. Możesz je założyć na stronie portal.librus.pl. Użyj danych, które wpisujesz w miejsca zaznaczone na obrazku. - Pomoc - Librus - Zaloguj się danymi swojego konta Librus (założonego wcześniej na swój adres e-mail). Nie należy logować się danymi otrzymanymi ze swojej szkoły. W razie problemów skorzystaj z przycisku pod formularzem. - Zaloguj się - Librus + LIBRUS® - logowanie + Aby korzystać z aplikacji musisz posiadać konto LIBRUS® Rodzina. Możesz je założyć na stronie portal.librus.pl. Użyj danych, które wpisujesz w miejsca zaznaczone na obrazku. + Pomoc do logowania LIBRUS® + Zaloguj się tokenem i kodem PIN, który można wygenerować po zalogowanu się do LIBRUS® Synergia w zakładce Aplikacje mobilne. + Zaloguj się do LIBRUS® JST + Zaloguj się danymi swojego konta LIBRUS® Rodzina (założonego wcześniej na swój adres e-mail). Nie należy logować się danymi otrzymanymi ze swojej szkoły. W razie problemów skorzystaj z przycisku pod formularzem. + Zaloguj się do LIBRUS® Synergia Wystąpił błąd w migracji danych. Został on już zgłoszony, co oznacza, że postaram się go naprawić.\n\nMożesz zacząć korzystać z aplikacji, jednak w przypadku jakichś błędów spróbuj usunąć profil i zalogować się ponownie. Aplikacja otrzymała dużą aktualizację. Jeżeli będą występować jakieś problemy w działaniu, możesz się ze mną skontaktować używając pozycji Pomoc i opinie w menu. Szkolny.eu został zaktualizowany @@ -385,6 +641,8 @@ Loguję… Wybierz z jakiego e-dziennika korzysta Twoja szkoła. W późniejszych etapach będziesz mógł dodać kilka kont z różnych e-dzienników. Z jakiego e-dziennika korzystasz? + (uczeń) + (rodzic) Dodaj ucznia Musisz wybrać przynajmniej jeden profil, który chcesz zapisać w aplikacji. Nie wybrałeś żadnego profilu @@ -394,16 +652,15 @@ Na pewno wyłączyć rejestrację? Podczas synchronizacji wystąpił błąd. Kliknij przycisk na dole strony, aby go zgłosić.\n\nProfile zostały zapisane, tak więc aplikacja powinna działać poprawnie. W razie co, możesz skorzystać z opcji Pomoc i opinie w menu.\n Błąd synchronizacji - %s]]> - %s… + Synchronizuję profil Synchronizacja… Rejestracja urządzenia: - Zaloguj się w wersji komputerowej dziennika Vulcan, a następnie wybierz opcję Dostęp mobilny. Kliknij Zarejestruj urządzenie mobilne. Możesz wpisać otrzymane dane lub zeskanować kod QR i wpisać sam kod PIN. - Pomoc - Vulcan UONET+ + Zaloguj się w wersji komputerowej dziennika VULCAN®, a następnie wybierz opcję Dostęp mobilny. Kliknij Zarejestruj urządzenie mobilne. Możesz wpisać otrzymane dane lub zeskanować kod QR i wpisać sam kod PIN. + Pomoc - VULCAN® UONET+ Otrzymane dane: Skanuj kod QR Wybierz opcję Dostęp mobilny na stronie e-dziennika, zarejestruj nowe urządzenie i wpisz otrzymane dane. W razie problemów skorzystaj z przycisku pod formularzem. - Zaloguj się - Vulcan UONET+ + Zaloguj się - VULCAN® UONET+ Dodaj… Zmień widok Generuj blokowy plan lekcji @@ -422,57 +679,98 @@ Dzisiaj W Ś + Dodaj wydarzenie + Zapisz własne wydarzenie lub zadanie domowe w Terminarzu Dodaj nowego ucznia + Dodaj lub usuń karty Terminarz + Zmień widok Terminarza Tablica ogłoszeń Frekwencja + Ustawienia frekwencji + Debugowanie + Pomoc i opinie Zapisz plan lekcji jako obraz Generuje plan lekcji na cały tydzień Oceny + Sposób obliczania średniej końcoworocznej + Średnie ocen + Pokaż średnie semestralne i końcoworoczne + Kolor wyświetlanych ocen + Ustawienia ocen Symulator edycji ocen + Sortuj oceny Pomoc Strona główna Zadania domowe + Laboratorium Zarządzaj profilami Oznacz jako przeczytane Oznacz wszystko jako przeczytane + Wiadomość + Napisz wiadomość Wiadomości - Odebrane - Wysłane Zachowanie Powiadomienia + Usuń wszystkie + Wyczyszczono powiadomienia Ustaw numer w dzienniku Ustawienia Synchronizuj Synchronizuj wszystkie + Szablon Plan lekcji + Edytor planu lekcji + Przekazywanie powiadomień Nie można pobrać załącznika Wystąpił błąd wewnętrzny pobierania załącznika. Może to być spowodowane słabym połączeniem internetowym. - %s (%d%%) + Pobierz ponownie + %s (%.2fMB) %s (%s) %s + Czy na pewno chcesz wysłać wiadomość do wybranych odbiorców? + Potwierdź wysłanie wiadomości Dodaj załącznik Odrzuć wiadomość Zapisz wersję roboczą Wyślij + Ten odbiorca został już wybrany + Wybierz odbiorców + Sprawdź poprawność wybranych odbiorców Wybierz odbiorców z kategorii %s, których chcesz dodać do wiadomości. Dodaj odbiorców + Wyślij + Wpisz temat o długości co najmniej 3 znaków Temat + Wpisz treść wiadomości + Napisz wiadomość… Napisz wiadomość Do %s, %s + Czy chcesz usunąć wiadomość? + Spowoduje to przeniesienie wiadomości do zakładki \"Usunięte\" w aplikacji. Zmiany nie wpłyną na wiadomość w e-dzienniku (nie zostanie ona tam usunięta). Błąd pobierania wiadomości Wersja robocza + Nie masz żadnych wiadomości. Błąd pobierania listy odbiorców  %s, przeczytano: %s, %s]]>  %s, przeczytano: tak]]> -  %s]]> +  %s]]>  %s, przeczytano: nie]]> + Odpowiedz + %s o %s + Szukaj + Znaleziono %d wiadomości + Wiadomość została wysłana + Usunięte + Odebrane + Wysłane Więcej Spróbuj włączyć Wi-Fi lub dane pakietowe. Jesteś offline Dalej Nie + Nie ma dzisiaj żadnych lekcji! Brak uprawnień Nie, dzięki Nie teraz @@ -481,29 +779,35 @@ %s, punktów: %s Pochwały: Podsumowanie - ładowanie… - Podsumowanie - semestr %d  - Podsumowanie - cały rok  + Podsumowanie - semestr %d  + Podsumowanie - cały rok  Uwagi: Powiadomienie Nieobecność Nieobecność usprawiedliwiona - Ogłoszenie szkolne: %s + Ogłoszenie szkolne od %s: %s %1$s na lekcji %2$s z dnia %3$s %1$s z dnia %3$s Spóźnienie Spóźnienie usprawiedliwione + Nie udało się sprawdzić aktualizacji Powiadomienie o pobieraniu danych dla e-dziennika Pobieranie danych Powiadomienie o nowej zawartości w e-dzienniku Powiadomienia - Powiadomienie o nowej zawartości w e-dzienniku (bez dźwięku) - Powiadomienia (cisza) + Powiadomienie o nowej zawartości w e-dzienniku (bez dźwięku - podczas ciszy nocnej) + Powiadomienia (cisza nocna) Powiadomienia o nowych wersjach aplikacji Aktualizacje + Powiadomienia o problemie, który wymaga działania użytkownika (np. weryfikacja Captcha). Zalecane jest pozostawienie tej kategorii włączonej. + Wymagane działanie + Kliknij, aby zobaczyć wszystkie powiadomienia + Dzień wolny Pobieranie aktualizacji… %1$s dnia %2$s z %3$s - %1$s dnia %2$s nieznanego przedmiotu + %1$s dnia %2$s + Czy na pewno chcesz zastosować te ustawienia?\n\nNie będziesz widział informacji o niektórych danych, przez co możesz przeoczyć ważne komunikaty, wiadomości lub oceny.\n\nUstawienia zostaną zastosowane dla aktualnie otwartego profilu. Przerwij Szkolny.eu: błąd Błąd podczas pobierania danych dla profilu %s @@ -514,13 +818,13 @@ Nowa ocena (%s) z %s Zadanie domowe z %1$s na %2$s Zadanie domowe na %2$s - %s %s - %s - Dzisiaj %d to szczęśliwy numerek. - Szczęsliwy numerek na %s to %d. - Szczęsliwy numerek na jutro to %d. - Dzisiaj to Ty masz szczęśliwy numerek! (%d) - %s to Ty będziesz miał szczęśliwy numerek! (%d) - Jutro to Ty masz szczęśliwy numerek! (%d) + %s dnia %s - %s + Dzisiaj %2$d to szczęśliwy numerek. + Szczęsliwy numerek na %1$s to %2$d. + Szczęsliwy numerek na jutro to %2$d. + Dzisiaj to Ty masz szczęśliwy numerek! (%2$d) + %1$s to Ty będziesz miał szczęśliwy numerek! (%2$d) + Jutro to Ty masz szczęśliwy numerek! (%2$d) Nieodebrana wiadomość od %s: %s Nowe powiadomienia: %d Brak dostępnej aktualizacji @@ -532,7 +836,8 @@ %s udostępnił %s na %s: %s %s zmienił %s na %s: %s %s usunął %s na %s: %s - Szkolny.eu: %s + Nowa nieobecność nauczyciela dla %s + Szkolny.eu: %s Wpis frekwencji Archiwizacja profilu Błąd @@ -545,7 +850,10 @@ Nowe zadanie domowe Nowa wiadomość Udostępniono wydarzenie - Ocena ucznia + Udostępniono zadanie domowe + Nowa nieobecność nauczyciela + Wpis zachowania + Usunięto wydarzenie Wiadomość z serwera Zmiana planu zajęć Zmiana lekcji @@ -553,12 +861,20 @@ Szkolny.eu: aktualizacja Kliknij, aby pobrać wersję %s Dostępna aktualizacja + Logowanie do LIBRUS® Rodzina: wymagane rozwiązanie zadania Captcha. Kliknij, aby kontynuować logowanie do dziennika. + Problem, który uniemożliwia synchronizację musi być rozwiązany przez użytkownika. Kliknij, aby uzyskać więcej informacji. + Wymagane działanie w aplikacji Brak powiadomień. OK Otwórz Otwieranie %s Brak aplikacji otwierającej ten typ pliku + Inne + Musisz przyznać uprawnienia do zapisu plików w pamięci telefonu, aby móc pobrać załącznik.\n\nKliknij OK, aby przyznać uprawnienia. + Odmówiłeś aplikacji wymaganych uprawnień do wykonania tej czynności.\n\nAby przynać uprawnienia, otwórz sekcję Uprawnienia dla aplikacji Szkolny.eu w ustawieniach telefonu.\n\nKliknij OK, aby przejść do ustawień aplikacji. + Wymagane uprawnienia Proszę czekać + pl.szczodrzynski.edziennik_preferences pl.szczodrzynski.edziennik_profiles pl.szczodrzynski.edziennik_profile%d_register pl @@ -573,11 +889,15 @@ Usuń profil Potwierdź usunięcie Zamierzasz usunąć profil %s. Oznacza to usunięcie z aplikacji wszystkich danych powiązanych z tym profilem (ocen, planu lekcji, sprawdzianów…).\n\nWiększość danych będziesz mógł pobrać na nowo poprzez ponowne zalogowanie się.\n\nCzy rzeczywiście chcesz usunąć profil %s? + Skanuj kod QR Nigdy Później Oceń teraz Pokaż, że podoba ci się Szkolny.eu - oceń aplikację i spraw, by była jeszcze lepsza! Odśwież + Rejestracja jest automatyczna, jeśli ta opcja jest włączona. Pozwala na tworzenie i odbieranie wydarzeń udostępnionych innym uczniom z Twojej klasy. Dzięki temu, można dodawać do dziennika pozycje nie zapisane przez nauczyciela.\n\nUpewnij się, że zapoznałeś się z warunkami Polityki prywatności i akceptujesz jej postanowienia. + Rejestracja na serwerze + Pobieranie udostępnionych wydarzeń… Usuń Usunięto Zgłoś @@ -590,13 +910,21 @@ Dziennik zmian Albo lepiej nie klikaj… Kliknij, aby wywołać nieoczekiwany wyjątek + Dołącz do naszego serwera Discord! + Serwer Discord + Uwaga. Ta opcja może nie działać na niektórych urządzeniach oraz w niektórych fragmentach aplikacji. + Zmień język aplikacji + Polski + Język aplikacji Licencje open-source Polityka prywatności E-dziennik - © Kuba Szczodrzyński wrzesień 2018 - październik 2019 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - 2020 Kliknij, aby sprawdzić aktualizacje Aktualizacja Wersja + Zaloguj konto ucznia/rodzica w aplikacji + Dodaj nowego ucznia Więcej Przekazywanie powiadomień Zmień obrazek @@ -604,6 +932,8 @@ Podaj nazwę profilu Zmień nazwę profilu Profil + Wyłącz określone rodzaje powiadomień + Filtruj powiadomienia Pokazuj powiadomienia z tego profilu Powiadamiaj o nowych danych Usuń obrazek profilu @@ -646,6 +976,7 @@ Będziesz otrzymywać udostępnione sprawdziany od innych osób z twojej klasy. Udostępniaj sprawdziany w swojej klasie Włącz Udostępnianie wydarzeń + Pokazuj nieobecności nauczycieli w Terminarzu Tablica ogłoszeń Obecności/nieobecności Dni wolne klasy @@ -698,6 +1029,9 @@ Dostosuj przyciski menu bocznego Wyświetlaj menu po lewej stronie Pokaż menu boczne + Otwieraj menu przyciskiem wstecz + Dzwonią dzwonki sań + Pada śnieg, pada śnieg Różowy Systemowy Motyw @@ -710,6 +1044,9 @@ Zadania domowe Wiadomości Plan lekcji + Wystąpił błąd + Wg daty + Wg przedmiotu Wszystkie przedmioty %s - %s%% Semestr 1 @@ -718,7 +1055,7 @@ Jasne! Autoryzacja Tworzenie powiadomień - %s… + %s… Pobieranie konta Pobieranie listy kont Pobieranie certyfikatu @@ -729,8 +1066,8 @@ Synchronizuję Pobieranie informacji o koncie Pobieranie ogłoszeń szkolnych - Pobieranie kategorii obecności Pobieranie obecności + Pobieranie kategorii obecności Pobieranie kategorii ocen z zachowania Pobieranie ocen z zachowania Pobieranie kalendarza @@ -743,6 +1080,7 @@ Pobieranie wydarzeń Pobieranie sprawdzianów Pobieranie kategorii ocen + Pobieranie komentarzy ocen Pobieranie szczegółów ocen Pobieranie ocen Pobieranie zadań domowych @@ -756,6 +1094,7 @@ Pobieranie ocen punktowych Pobieranie proponowanych ocen Pobieranie zebrań z rodzicami + Pobieranie dni wolnych szkoły Pobieranie informacji o szkole Pobieranie udostępnionych wydarzeń Pobieranie przedmiotów @@ -779,8 +1118,8 @@ Podano nieprawidłową nazwę szkoły lub zawiera niedozwolone znaki. Podany adres serwera jest nieprawidłowy. Upewnij się, że jest wpisany poprawnie oraz nie zawiera żadnych spacji.\n\nWpisywany adres nie powinien zawierać części http://…mobidziennik.pl. Podano nieprawidłowy token. - Konto LIBRUS utraciło połączenie z kontem Synergia. Zaloguj się na stronie portal.librus.pl lub w oficjalnej aplikacji Librus i postępuj zgodnie z instrukcją, aby naprawić konto. - Konto LIBRUS nie zostało aktywowane. Aktywuj konto korzystając z otrzymanego e-maila. + Konto LIBRUS® Rodzina utraciło połączenie z kontem LIBRUS® Synergia. Zaloguj się na stronie portal.librus.pl lub w oficjalnej aplikacji "LIBRUS®" i postępuj zgodnie z instrukcją, aby naprawić konto. + Konto LIBRUS® Rodzina nie zostało aktywowane. Aktywuj konto korzystając z otrzymanego e-maila. Bład logowania Chwilowa przerwa techniczna\n\nSpróbuj ponownie później Nie znaleziono adresu API. Spróbuj zalogować urządzenie ponownie. @@ -793,6 +1132,7 @@ Uczeń %s nie jest przypisany do tego konta w dzienniku (login: %s).\n\nSpróbuj zalogować się ponownie, aczkolwiek może to oznaczać przerwę techniczną e-dziennika. Błąd podczas zapisywania danych. Zgłoś błąd deweloperowi. Nie można nawiązać bezpiecznego połączenia. + Konto LIBRUS® Synergia nie zostało potwierdzone przez rodzica/opiekuna prawnego.\n\nZaloguj się na stronie portal.librus.pl i postępuj zgodnie z instrukcjami. Przekroczono czas oczekiwania.\n\nNiewystarczająca jakość połączenia internetowego lub przerwa techniczna e-dziennika Nieznany błąd Pobieram terminarz… @@ -814,6 +1154,9 @@ Dołącz odpowiedź API (zalecane) Zgłaszanie błędu Usługa synchronizacji + model + position + viewType Kliknij na ikonę, aby oznaczyć wszystkie dane jako przeczytane. Powiadomienia Kliknij na nagłówek menu, aby zmienić lub dodać profil ucznia. Znajdziesz tam też ręczną synchronizację wszystkich profili. @@ -823,29 +1166,88 @@ Inni Rodzic Rada rodziców - Pedagog / psycholog + Pedagog / Psycholog Dyrektor - Szkolny administrator + Szkolny Administrator Szkolna rada rodziców Sekretariat + Specjaliści Uczeń Administrator / SuperAdministrator Nauczyciel Przeglądaj kategorię pojutrze przedwczoraj + Bursztynowy + Czarny / OLED + Niebieski + Czekoladowy + Ciemny + Ciemny niebieski + Ciemny zielony + Ciemny fioletowy + Ciemny czerwony + Indygo + Jasny + Jasny niebieski + Jasny zielony + Jasny fioletowy + Jasny czerwony + Jasny żółty + Fioletowy + Czerwony + Pokaż plan lekcji + W tym dniu nie ma lekcji: + Dzień wolny Ze zmianami na ten tydzień (%s do %s) Do wydruku (bez zmian, oszczędza kolory) Wyświetl nazwę profilu ucznia Ze zmianami na następny tydzień (%s do %s) Bez zmian lekcji + Do wydruku (oszczędza kolory) Może to potrwać kilkanaście sekund… Generowanie planu lekcji Zakres generowanego planu + Na wybrany tydzień + Pokaż nazwę profilu + Pokaż imiona i nazwiska nauczycieli Plan lekcji w formie obrazka został zapisany w katalogu Szkolny.eu.\n\nMożesz go od razu otworzyć albo udostępnić. Gotowe + Lekcja odwołana + Zastępstwo + Zastępstwo: zamiast %s + Lekcja przeniesiona na inny termin + Lekcja przeniesiona z innego terminu + Lekcja przeniesiona z dnia %s, godz. %s + Lekcja przeniesiona z godz. %s + Lekcja przeniesiona na %s, godz. %s + Lekcja przeniesiona na godz. %s + Data + Wybierz datę + Wybierz przedmiot + Godzina + Wybierz godzinę + %s (lekcja %d) + Nie dodano własnych lekcji na ten dzień. + Na każdej lekcji przedmiotu %s + Obejmuje plan lekcji na każdy tydzień + Wg. przedmiotu + Jednorazowo + Cyklicznie (brak sali) Brak lekcji tego dnia + Brak lekcji tego dnia + (brak nazwy) + Pobierz plan lekcji + Nie pobrano planu lekcji na ten tydzień. + Brak planu lekcji + na tydzień %s + Skontaktuj się z wychowawcą w celu udostępnienia planu lekcji. + Plan lekcji nie został opublikowany przez szkołę. + Brak planu lekcji + Wybierz dzień + Pobieranie planu lekcji na wybrany tydzień… + Dzisiaj Synchronizuj wszystko Debugowanie Pomoc i opinie @@ -856,6 +1258,10 @@ Menadżer profili Zmień semestr Zmieniam profil ucznia + dzisiaj + jutro + Synchronizuję… + [%d%%] %s transition_name Nieznany Wymagana aktualizacja @@ -863,10 +1269,13 @@ Próbujesz uruchomić starszą wersję aplikacji, co może uszkodzić jej dane. Sparowane przeglądarki Brak połączenia z serwerem. + Połączono %s Brak sparowanych przeglądarek. Sparuj przeglądarkę Czy na pewno chcesz usunąć tą przeglądarkę? + Token nie wygląda na prawidłowy Nie możesz tego zrobić + Odłącz Musisz włączyć rejestrację automatyczną, by korzystać z przekazywania powiadomień.\n\nZaznacz \"Zezwól na rejestrację\" w ustawieniach logowania. Co chcesz zrobić? Co to jest? @@ -884,146 +1293,89 @@ Synchronizuj Użyj większej czcionki Brak lekcji tego dnia. + Brak lekcji przez następne 7 dni. + Plan lekcji nie został pobrany.\n\nOtwórz aplikację i wykonaj synchronizację, aby pobrać plan lekcji. Wybrany profil nie istnieje.\nUsuń widget i spróbuj ponownie. + Brak lekcji przez nast. 7 dni. + Nie pobrano planu lekcji. Plan lekcji Łączony plan lekcji Tak wczoraj Jesteś offline. Spróbuj włączyć Wi-Fi lub dane komórkowe. Połączenie sieciowe - Wiadomość - Dodaj wydarzenie - Zapisz własne wydarzenie lub zadanie domowe w Terminarzu - Zmień widok Terminarza - Średnie ocen - Pokaż średnie semestralne i końcoworoczne - Kolor wyświetlanych ocen - Wg daty - Wg przedmiotu - Sposób obliczania średniej końcoworocznej - Sortuj oceny - Konto Synergia nie zostało potwierdzone przez rodzica/opiekuna prawnego.\n\nZaloguj się na stronie portal.librus.pl i postępuj zgodnie z instrukcjami. - Pomoc i opinie - Podano nieprawidłowy moduł: %d Zgłoś błąd programiście - Serwer Discord - Dołącz do naszego serwera Discord! - Debugowanie - Logowanie do Portalu Librus - Logowanie do API - Logowanie do Librus Synergia - Logowanie do wiadomości Librus - Usługa synchronizacji - Dzięki niej, aplikacja Szkolny.eu może synchronizować dane z e-dziennikiem. Możesz ją zamknąć, ponieważ w tej chwili nic nie robi. - Zamknij - Anuluj - Trwa synchronizacja profilu %s... - Synchronizacja przerwana - Zgłaszanie błędów... - Zgłaszanie błędów - Pobieram informacje o uczniu... - Pobieranie dni wolnych szkoły - Aktualne - Minione - Brak zadań domowych. - Nieobecni nauczyciele - Pobieranie komentarzy ocen - Pokazuj nieobecności nauczycieli w Terminarzu - Pobieram informacje o szkole... - Pobieranie ocen ucznia... - Pobieranie kategorii ocen... - Logowanie do Template WEB... - Logowanie do Template API... - Logowanie do MobiDziennika... - Logowanie do API MobiDziennika... - Pobieram dane... - Pobieram wiadomości odebrane... - Pobieram wiadomości... - Pobieram szczęśliwy numerek... - Pobieram kalendarz... - Pobieram uwagi ucznia... - Pobieram frekwencję ucznia... - Logowanie do Vulcan API... - Logowanie do API MobiDziennika... - Pobieranie wydarzeń kalendarza... - Pobieranie zadań domowych... - Pobieranie kategorii obecności... - Pobieranie ogłoszeń szkolnych... - Pierwsze logowanie - Pobieranie informacji o klasie... - Pobieranie nieobecności nauczycieli... - Pobieranie rodzajów nieobecności nauczycieli... - Pobieranie słowników... - Synchronizuję... - [%d%%] %s - Pobieranie uwag... - Pobieranie grup klasowych... - Pobieranie informacji o jednostce... - Pobieranie listy nauczycieli... - Pobieranie listy przedmiotów... - Pobieranie listy sal lekcyjnych... - Pobieranie kategorii wydarzeń... - Pobieranie kategorii uwag... - Pobieranie zebrań z rodzicami... - Logowanie do iDziennika... - Logowanie do iDziennika... - Pobieranie wiadomości wysłanych... - (rodzic) - (uczeń) - Język aplikacji - Polski - Zmień język aplikacji - Uwaga. Ta opcja może nie działać na niektórych urządzeniach oraz w niektórych fragmentach aplikacji. - Według systemu - Polski - English - Pobieranie planu lekcji... - Pobieranie ocen proponowanych... - Pobieranie listy sprawdzianów... - Wykryto problem z synchronizacją - Na urządzeniu prawdopodobnie zainstalowany jest menedżer aplikacji, który może powodować problemy z synchronizacją automatyczną.\n\nNależy wyłączyć w ustawieniach telefonu optymalizację baterii dla aplikacji Szkolny.eu.\n\nKliknij OK, aby przejść do ustawień telefonu. - Nie pytaj ponownie - Nie udało się otworzyć ustawień - Tworzenie powiadomień - Librus - logowanie - Dzisiaj - Lekcja odwołana - Zastępstwo - Zastępstwo: zamiast %s - Lekcja przeniesiona na godz. %s - Lekcja przeniesiona na %s, godz. %s - Lekcja przeniesiona z godz. %s - Lekcja przeniesiona z dnia %s, godz. %s - Lekcja przeniesiona na inny termin - Lekcja przeniesiona z innego terminu - Brak planu lekcji - Plan lekcji nie został opublikowany przez szkołę. - Skontaktuj się z wychowawcą w celu udostępnienia planu lekcji. - Dzień wolny - W tym dniu nie ma lekcji: - Pokaż plan lekcji - Brak lekcji tego dnia - Brak planu lekcji - Nie pobrano planu lekcji na ten tydzień. - Pobierz plan lekcji - na tydzień %s - Nauczyciel - Sala lekcyjna - Grupa - ID lekcji - Nr lekcji - Lekcja przeniesiona na %s - Lekcja przeniesiona z %s - Dodaj wpis do terminarza - Lekcja/godzina - nast. lekcja %s - jutro (%s) - %s (%s) - -- inna data -- - dzisiaj (%s) - następny %s (%s) - Nie ma lekcji tego dnia - Profil został usunięty. - Wystąpił błąd - Synchronizacja ręczna - (brak nazwy) + Wg typu + %d przez cały rok + %d w tym semestrze + Nauczyciel + Rodzaj + Data + Godzina + Temat lekcji + Liczona do puli? + ID frekwencji + ID rodzaju podstawowego + Jaki masz e-dziennik w szkole? + Wybierz z jakiego e-dziennika korzysta Twoja szkoła. Jeśli masz kilka kont w różnych dziennikach, będziesz mógł je dodać później. + LIBRUS® Rodzina / Synergia + Zaloguj używając e-maila + Musisz posiadać konto LIBRUS® Rodzina + Zaloguj używając loginu i hasła + Użyj loginu w postaci \"9874123u\" + Logowanie przez platformę VULCAN® + Tylko Oświata w Radomiu oraz Innowacyjny Tarnobrzeg + VULCAN® UONET+ + Użyj tokenu, symbolu i kodu PIN + Zarejestruj urządzenie na stronie dziennika VULCAN® + Użyj e-maila/nazwy użytkownika i hasła + Zaloguj danymi, które podajesz na stronie e-dziennika VULCAN® + MobiDziennik + Zaloguj nazwą serwera, loginem i hasłem + Podaj dane, których używasz na stronie e-dziennika + W jaki sposób się logujesz do dziennika? + Wybierz, który obrazek odpowiada temu, który widzisz podczas logowania na stronie internetowej swojego dziennika.\n\nJeżeli Twoja szkoła nie korzysta z żadnej z tych platform miejskich, wybierz pierwszą opcję. + Ładowanie listy e-dzienników… + Jeśli trwa to za długo, sprawdź swoje połączenie internetowe i zrestartuj aplikację. + Zaloguj się - %s + Zaloguj się swoim kontem LIBRUS® Rodzina, które działa w oficjalnej aplikacji "LIBRUS®" oraz na stronie portal.librus.pl, w niebieskim formularzu.\n\nJeśli nie masz konta LIBRUS® Rodzina, możesz je założyć na stronie https://portal.librus.pl/rodzina/register. + Podaj login otrzymany od szkoły, którym logujesz się do LIBRUS® Synergia (fioletowy formularz).\n\nZalecane jest logowanie kontem LIBRUS® Rodzina (używając e-maila) w poprzednim kroku. + Zaloguj się do LIBRUS® Synergia na komputerze, wybierz zakładkę Aplikacje Mobilne, następnie wpisz otrzymany Token i PIN poniżej. + Zaloguj się do dziennika VULCAN® na komputerze, wybierz zakładkę Dostęp Mobilny, kliknij przycisk Zarejestruj urządzenie mobilne. Podaj otrzymany Token, Symbol i PIN w polach poniżej. + Podaj dane, którymi logujesz się na stronie internetowej dziennika VULCAN® lub na miejskiej platformie. + Podaj dane, których używasz do logowania na stronie MobiDziennika. Jako adres serwera możesz wpisać adres strony internetowej, na której masz MobiDziennik. + Logowanie do dziennika VULCAN®... + iDziennik Progman / iUczniowie + Zaloguj używając nazwy użytkownika i hasła + Podaj dane, których używasz na stronie internetowej e-dziennika + Użyj danych, które wpisujesz w formularz na stronie iDziennika. Jeśli nie pamiętasz hasła, wejdź na http://iuczniowie.progman.pl/ i kliknij przycisk \"Zapomniałem hasła\". + EduDziennik + Zaloguj używając e-maila i hasła + Użyj danych, które podajesz na stronie internetowej e-dziennika + Podaj adres e-mail i hasło, których używasz do logowania w przeglądarce na stronie EduDziennika. + Podlaska Platforma Edukacyjna + Zaloguj używając tokenu + Podaj token aplikacji mobilnej. + Logowanie do PPE… + Profil jest archiwalny + Przeglądasz dane ucznia z poprzedniego roku szkolnego (%d/%d). Synchronizacja oraz pobieranie wiadomości i niektórych zadań domowych zostały wyłączone.\n\nAby otworzyć profil ucznia z aktualnego roku, wybierz "Zamknij archiwum" na stronie głównej. + Wakacje ;) + Prawdopodobnie rok szkolny dla tego ucznia jeszcze się nie zaczął (zacznie się %s). Spróbuj wykonać synchronizację później. + Koniec roku szkolnego + Rok szkolny zakończył się %s. Dane ucznia z poprzedniego roku zostaną przeniesione do archiwum, aby można było je później przeglądać. + Profil archiwalny + Przeglądasz dane ucznia z roku szkolnego %d/%d. + Zamknij archiwum + Brak aktualnego profilu + Uczeń %s nie posiada profilu na tym koncie w aktualnym roku szkolnym. Prawdopodobnie ten profil został usunięty lub uczeń nie uczęszcza już do tej klasy.\n\nAby przejść do aktualnego profilu, wybierz ucznia z listy lub zaloguj się na jego konto przyciskiem Dodaj ucznia. + Znaki towarowe zamieszczone w tej aplikacji należą do ich prawowitych właścicieli i są używane wyłącznie w celach informacyjnych. + Dostępna aktualiacja aplikacji + Używasz starej wersji aplikacji Szkolny.eu (%s). Aby móc korzystać z aplikacji oraz zapewnić najlepsze działanie, zaktualizuj aplikację do wersji %s.\n\nDziennik zmian:\n%s + Posiadasz nieaktualną wersję aplikacji Szkolny.eu. Aby móc dalej synchronizować dane, musisz zaktualizować aplikację. + Aktualizuj + Nie teraz + Dostępna aktualizacja + Zaktualizuj aplikację do najnowszej wersji %s. + Zobacz więcej + Aktualizuj + Dowiedz się więcej diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 54fcb154..9983ecf0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -10,78 +10,100 @@ @drawable/dead_background - - + + + + diff --git a/app/src/main/res/xml/widget_lucky_number_info.xml b/app/src/main/res/xml/widget_lucky_number_info.xml index 18721b35..813afadc 100644 --- a/app/src/main/res/xml/widget_lucky_number_info.xml +++ b/app/src/main/res/xml/widget_lucky_number_info.xml @@ -11,5 +11,5 @@ android:resizeMode="horizontal|vertical" android:updatePeriodMillis="1800000" android:widgetCategory="home_screen" - android:configure="pl.szczodrzynski.edziennik.widgets.WidgetConfigActivity" + android:configure="pl.szczodrzynski.edziennik.ui.widgets.WidgetConfigActivity" tools:ignore="UnusedAttribute" /> diff --git a/app/src/main/res/xml/widget_notifications_info.xml b/app/src/main/res/xml/widget_notifications_info.xml index e15ea4b9..4720f1b1 100644 --- a/app/src/main/res/xml/widget_notifications_info.xml +++ b/app/src/main/res/xml/widget_notifications_info.xml @@ -11,5 +11,5 @@ android:resizeMode="horizontal|vertical" android:updatePeriodMillis="5400000" android:widgetCategory="home_screen" - android:configure="pl.szczodrzynski.edziennik.widgets.WidgetConfigActivity" + android:configure="pl.szczodrzynski.edziennik.ui.widgets.WidgetConfigActivity" tools:ignore="UnusedAttribute" /> diff --git a/app/src/main/res/xml/widget_timetable_info.xml b/app/src/main/res/xml/widget_timetable_info.xml index a549737f..c0a5b3d4 100644 --- a/app/src/main/res/xml/widget_timetable_info.xml +++ b/app/src/main/res/xml/widget_timetable_info.xml @@ -11,5 +11,5 @@ android:resizeMode="horizontal|vertical" android:updatePeriodMillis="1800000" android:widgetCategory="home_screen" - android:configure="pl.szczodrzynski.edziennik.widgets.WidgetConfigActivity" + android:configure="pl.szczodrzynski.edziennik.ui.widgets.WidgetConfigActivity" tools:ignore="UnusedAttribute" /> diff --git a/build.gradle b/build.gradle index d8a75040..1f63c6ba 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,11 @@ buildscript { ext { - kotlin_version = '1.3.50' + kotlin_version = '1.3.61' release = [ - versionName: "3.9.8-dev", - versionCode: 3090800 + versionName: "4.4.1", + versionCode: 4040199 ] setup = [ @@ -17,37 +17,41 @@ buildscript { ] versions = [ - kotlin : "1.3.50", - ktx : "1.1.0", + gradleAndroid : '4.1.0-rc01', + + kotlin : ext.kotlin_version, + ktx : "1.2.0", androidX : '1.0.0', annotation : '1.1.0', - recyclerView : '1.1.0-rc01', - material : '1.2.0-alpha01', - appcompat : '1.1.0', - constraintLayout : '2.0.0-beta3', + recyclerView : '1.2.0-alpha01', + material : '1.2.0-alpha05', + appcompat : '1.2.0-alpha02', + constraintLayout : '2.0.0-beta4', cardview : '1.0.0', gridLayout : '1.0.0', navigation : "2.0.0", navigationFragment: "1.0.0", legacy : "1.0.0", - room : "2.2.1", - lifecycle : "2.2.0-rc02", - work : "2.2.0", + room : "2.2.5", + lifecycle : "2.2.0", + work : "2.3.4", - firebase : '17.2.0', - firebasemessaging: "20.0.0", + firebase : '17.2.2', + firebasemessaging: "20.1.3", play_services : "17.0.0", materialdialogs : "0.9.6.0", - materialdrawer : "cad66092a6", - iconics : "4.0.1-b02", + materialdrawer : "817e45765c367034b03046aaea6e95eeabcb40e9", + iconics : "4.0.1", font_cmd : "3.5.95.1-kotlin", - navlib : "8ae5e2b87a", + navlib : "28cdab341470dffa5f331379fe9702482681d7de", - gifdrawable : "1.2.15" + gifdrawable : "1.2.15", + + retrofit : "2.6.4" ] } @@ -59,11 +63,11 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath "com.android.tools.build:gradle:${versions.gradleAndroid}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath 'me.tatarka:gradle-retrolambda:3.7.0' - classpath 'com.google.gms:google-services:4.3.1' + classpath 'com.google.gms:google-services:4.3.3' classpath 'io.fabric.tools:gradle:1.28.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -83,26 +87,10 @@ allprojects { maven { url 'https://jitpack.io' } maven { url "https://kotlin.bintray.com/kotlinx/" } maven { url "https://dl.bintray.com/wulkanowy/wulkanowy" } + maven { url "https://dl.bintray.com/undervoid/PowerPermission" } } } -ext { - compileSdkVersion = 28 - buildToolsVersion = '28.0.3' - targetSdkVersion = compileSdkVersion - minSdkVersion = 16 - - androidXAppCompat = '1.1.0-beta01' - androidXRecyclerView = '1.1.0-alpha06' - androidXCardView = '1.0.0' - androidXGridLayout = '1.0.0' - androidXConstraintLayout = '1.1.3' - googleMaterial = '1.1.0-alpha07' - - iconics = '4.0.1-b01' - kotlin = '1.3.41' -} - task clean(type: Delete) { delete rootProject.buildDir } diff --git a/cafebar/build.gradle b/cafebar/build.gradle index 31b0b482..4e992077 100644 --- a/cafebar/build.gradle +++ b/cafebar/build.gradle @@ -21,11 +21,11 @@ apply plugin: 'com.android.library' group = 'com.github.danimahardhika' android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion 16 + targetSdkVersion setup.targetSdk versionCode 132 versionName "1.3.2" vectorDrawables.useSupportLibrary = true @@ -58,7 +58,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "com.google.android.material:material:${googleMaterial}" - implementation "androidx.cardview:cardview:${androidXCardView}" - implementation "androidx.appcompat:appcompat:${androidXAppCompat}" + implementation "com.google.android.material:material:${versions.material}" + implementation "androidx.cardview:cardview:${versions.cardview}" + implementation "androidx.appcompat:appcompat:${versions.appcompat}" } diff --git a/codegen/.gitignore b/codegen/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/codegen/.gitignore @@ -0,0 +1 @@ +/build diff --git a/codegen/build.gradle b/codegen/build.gradle new file mode 100644 index 00000000..e2630bd0 --- /dev/null +++ b/codegen/build.gradle @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' + +kapt { + generateStubs = true +} + +sourceSets { + main { + java { + srcDir "${buildDir.absolutePath}/tmp/kapt/main/kotlinGenerated/" + } + } +} + + +dependencies { + kapt project(":annotation") + compileOnly project(':annotation') + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + // configuration generator for service providers + implementation "com.google.auto.service:auto-service:1.0-rc4" + kapt "com.google.auto.service:auto-service:1.0-rc4" + kapt "androidx.room:room-compiler:${versions.room}" + implementation "androidx.room:room-runtime:${versions.room}" + implementation "com.squareup:kotlinpoet:1.5.0" + implementation "androidx.sqlite:sqlite:2.1.0@aar" + +} + +sourceCompatibility = "7" +targetCompatibility = "7" diff --git a/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt b/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt new file mode 100644 index 00000000..5b5b1f71 --- /dev/null +++ b/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt @@ -0,0 +1,342 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +package pl.szczodrzynski.edziennik.codegen + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.TypeConverters +import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import java.io.File +import javax.annotation.processing.* +import javax.lang.model.SourceVersion +import javax.lang.model.element.* +import javax.lang.model.type.* +import javax.lang.model.util.ElementFilter +import javax.tools.Diagnostic +import kotlin.reflect.KClass + +@Suppress("unused") +@AutoService(Processor::class) +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedOptions(FileGenerator.KAPT_KOTLIN_GENERATED_OPTION_NAME) +class FileGenerator : AbstractProcessor() { + companion object { + const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" + } + + private data class TypeConverter(val dataType: TypeMirror, val converterType: TypeElement, val methodName: Name, val returnType: TypeMirror) + + private inline fun Element.getAnnotationClassValue(f: T.() -> KClass<*>) = try { + getAnnotation(T::class.java).f() + throw Exception("Expected to get a MirroredTypeException") + } catch (e: MirroredTypeException) { + e.typeMirror + } + private inline fun Element.getAnnotationClassValues(f: T.() -> Array>) = try { + getAnnotation(T::class.java).f() + throw Exception("Expected to get a MirroredTypesException") + } catch (e: MirroredTypesException) { + e.typeMirrors + } + + override fun process(set: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean { + roundEnvironment?.getElementsAnnotatedWith(SelectiveDao::class.java)?.forEach { it -> + if (it.kind != ElementKind.CLASS) { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can only be applied to classes, element: $it") + return false + } + + val generatedSourcesRoot = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] + if (generatedSourcesRoot?.isEmpty() != false) { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can't find the target directory for generated Kotlin files.") + return false + } + + val file = File(generatedSourcesRoot) + file.mkdirs() + + val dao = it as TypeElement + processClass(dao, file) + + //processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "package = $packageName, className = $className, methodName = $methodName, tableName = $tableName, paramName = $paramName, paramClass = $paramClass") + } + return true + } + + private fun processClass(dao: TypeElement, file: File) { + val daoName = dao.simpleName.toString() + val packageName = processingEnv.elementUtils.getPackageOf(dao).toString() + + val dbType = processingEnv.typeUtils.asElement(dao.getAnnotationClassValue { db }) as TypeElement + val typeConverters = dbType.getAnnotationClassValues { value }.map { + processingEnv.typeUtils.asElement(it) as TypeElement + }.map { type -> + processingEnv.elementUtils.getAllMembers(type).mapNotNull { element -> + if (element is ExecutableElement) { + if (element.returnType.toString() == "java.lang.String" + || element.returnType.toString() == "java.lang.Long" + || element.returnType.toString() == "java.lang.Integer" + || element.returnType.kind.isPrimitive) { + if (element.simpleName.startsWith("to") && element.parameters.isNotEmpty()) + return@mapNotNull TypeConverter(element.parameters.first().asType(), type, element.simpleName, element.returnType) + } + } + null + } + }.flatten() + + //processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "c = ${typeConverters.joinToString()}") + + val roomDatabase = ClassName("androidx.room", "RoomDatabase") + val selective = TypeSpec.classBuilder("${daoName}Selective") + .primaryConstructor(FunSpec.constructorBuilder() + .addParameter("__db", roomDatabase, KModifier.PRIVATE) + .build()) + .addProperty(PropertySpec.builder("__db", roomDatabase) + .initializer("__db") + .addModifiers(KModifier.PRIVATE) + .build()) + + val usedTypeConverters = mutableSetOf() + + processingEnv.elementUtils.getAllMembers(dao).forEach { element -> + if (element.kind != ElementKind.METHOD) + return@forEach + val method = element as ExecutableElement + val annotation = method.getAnnotation(UpdateSelective::class.java) ?: return@forEach + usedTypeConverters.addAll(processMethod(selective, method, annotation, typeConverters)) + } + + usedTypeConverters.forEach { converter -> + selective.addProperty(PropertySpec.builder("__${converter.converterType.simpleName}", converter.converterType.asType().asTypeName(), KModifier.PRIVATE) + .delegate(CodeBlock.builder() + .beginControlFlow("lazy") + .addStatement("%T()", converter.converterType.asType().asTypeName()) + .endControlFlow() + .build()) + .build()) + } + + FileSpec.builder(packageName, "${daoName}Selective") + .addType(selective.build()) + .build() + .writeTo(file) + } + + private fun VariableElement.name() = getAnnotation(ColumnInfo::class.java)?.name ?: simpleName.toString() + + private fun processMethod(cls: TypeSpec.Builder, method: ExecutableElement, annotation: UpdateSelective, typeConverters: List): List { + val methodName = method.simpleName.toString() + val parameter = method.parameters.first() + val paramName = parameter.simpleName.toString() + val paramTypeElement = processingEnv.typeUtils.asElement(parameter.asType()) as TypeElement + val paramTypeAnnotation = paramTypeElement.getAnnotation(Entity::class.java) + + val tableName = paramTypeAnnotation.tableName + val primaryKeys = annotation.primaryKeys + val skippedColumns = annotation.skippedColumns + + + var members = processingEnv.elementUtils.getAllMembers(paramTypeElement) + val allFields = ElementFilter.fieldsIn(members) + + // check all super classes + var superType = paramTypeElement.superclass + while (superType !is NoType) { + val superTypeElement = processingEnv.typeUtils.asElement(superType) as TypeElement + members = processingEnv.elementUtils.getAllMembers(superTypeElement) + allFields += ElementFilter.fieldsIn(members) + superType = superTypeElement.superclass + } + + allFields.removeAll { skippedColumns.contains(it.name()) } + allFields.removeAll { it.getAnnotation(Ignore::class.java) != null } + allFields.removeAll { field -> field.modifiers.any { it == Modifier.STATIC } } + val allFieldsDistinct = allFields.distinct() + + // dump fields + //processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, allFieldsDistinct.joinToString()) + + val fields = allFieldsDistinct.filterNot { primaryKeys.contains(it.name()) } + val primaryFields = allFieldsDistinct.filter { primaryKeys.contains(it.name()) } + val fieldNames = fields.map { it.name() } + val primaryFieldNames = primaryFields.map { it.name() } + + val fieldNamesQuery = fieldNames.joinToString { "$it = ?" } + val primaryFieldNamesQuery = primaryFieldNames.joinToString(" AND ") { "$it = ?" } + val query = "\"\"\"UPDATE $tableName SET $fieldNamesQuery WHERE $primaryFieldNamesQuery\"\"\"" + + val entityInsertionAdapter = ClassName("androidx.room", "EntityInsertionAdapter") + val supportSQLiteStatement = ClassName("androidx.sqlite.db", "SupportSQLiteStatement") + + val usedTypeConverters = mutableListOf() + + val bind = CodeBlock.builder() + (fields+primaryFields).forEachIndexed { i, field -> + val index = i+1 + val fieldName = field.simpleName.toString() + val name = "${paramName}_$fieldName" + val realName = "${paramName}.$fieldName" + val nullable = field.getAnnotation(org.jetbrains.annotations.Nullable::class.java) != null + + var param = when (field.asType().kind) { + TypeKind.BOOLEAN -> "if ($name) 1L else 0L" + TypeKind.BYTE, + TypeKind.SHORT, + TypeKind.INT -> "$name.toLong()" + TypeKind.CHAR -> "$name.toString()" + TypeKind.FLOAT -> "$name.toDouble()" + else -> when (field.asType().toString()) { + "java.lang.String" -> name + "java.lang.Boolean" -> "if ($name == true) 1L else 0L" + "java.lang.Byte", + "java.lang.Short", + "java.lang.Integer" -> "$name.toLong()" + "java.lang.Long" -> name + "java.lang.Char" -> "$name.toString()" + "java.lang.Float" -> "$name.toDouble()" + "java.lang.Double" -> name + else -> name + } + } + + var isConvert = false + val bindMethod = when (field.asType().kind) { + TypeKind.BOOLEAN -> "bindLong" + TypeKind.BYTE -> "bindLong" + TypeKind.SHORT -> "bindLong" + TypeKind.INT -> "bindLong" + TypeKind.LONG -> "bindLong" + TypeKind.CHAR -> "bindString" + TypeKind.FLOAT -> "bindDouble" + TypeKind.DOUBLE -> "bindDouble" + else -> when (field.asType().toString()) { + "java.lang.String" -> "bindString" + "java.lang.Boolean" -> "bindLong" + "java.lang.Byte" -> "bindLong" + "java.lang.Short" -> "bindLong" + "java.lang.Integer" -> "bindLong" + "java.lang.Long" -> "bindLong" + "java.lang.Char" -> "bindString" + "java.lang.Float" -> "bindDouble" + "java.lang.Double" -> "bindDouble" + else -> { + val converter = typeConverters.firstOrNull { + it.dataType.toString() == field.asType().toString() + } + if (converter != null) { + param = "__${converter.converterType.simpleName}.${converter.methodName}($realName)" + param = when (converter.returnType.toString()) { + "java.lang.Integer", "int", + "java.lang.Short", "short", + "java.lang.Byte", "byte" -> "$param.toLong()" + "java.lang.Boolean", "boolean" -> "if ($param) 1L else 0L" + "java.lang.Char", "char" -> "$param.toString()" + "java.lang.Float", "float" -> "$param.toDouble()" + else -> param + } + isConvert = true + usedTypeConverters += converter + when (converter.returnType.toString()) { + "java.lang.Integer", "int", + "java.lang.Short", "short", + "java.lang.Byte", "byte", + "java.lang.Boolean", "boolean" -> "bindLong" + "java.lang.Char", "char" -> "bindString" + "java.lang.Float", "float" -> "bindDouble" + else -> "bindString" + } + } + else "bind${field.asType()}" + } + } + } + + if (!isConvert) { + bind.addStatement("val $name = $realName") + } + else { + bind.addStatement("val $name = $param") + param = name + } + if (nullable) { + bind.beginControlFlow("if ($name == null)") + .addStatement("stmt.bindNull($index)") + .endControlFlow() + .beginControlFlow("else") + .addStatement("stmt.$bindMethod($index, $param)") + .endControlFlow() + } + else { + bind.addStatement("stmt.$bindMethod($index, $param)") + } + } + + val adapterName = "__insertionAdapterOf$methodName" + val delegate = CodeBlock.builder().add(""" + |lazy { + | object : EntityInsertionAdapter<%T>(__db) { + | override fun createQuery() = $query + | override fun bind(stmt: %T, $paramName: %T) { + |${bind.indent().indent().indent().build()} + | } + | } + |}""".trimMargin(), paramTypeElement.asClassName(), supportSQLiteStatement, paramTypeElement.asClassName()) + + cls.addProperty(PropertySpec.builder(adapterName, entityInsertionAdapter.parameterizedBy(paramTypeElement.asClassName()), KModifier.PRIVATE) + .delegate(delegate.build()) + .build()) + + val list = ClassName("kotlin.collections", "List") + val longArray = ClassName("kotlin", "LongArray") + + val function = FunSpec.builder(methodName) + .addModifiers(KModifier.INTERNAL) + .addParameter("item", parameter.asType().asTypeName()) + .returns(Long::class.java) + .addStatement("__db.assertNotSuspendingTransaction()") + .addStatement("__db.beginTransaction()") + .addCode(""" + |try { + | val _result = $adapterName.insertAndReturnId(item) + | __db.setTransactionSuccessful() + | return _result + |} finally { + | __db.endTransaction() + |} + """.trimMargin()) + .build() + + val functionAll = FunSpec.builder(methodName+"All") + .addModifiers(KModifier.INTERNAL) + .addParameter("items", list.parameterizedBy(parameter.asType().asTypeName())) + .returns(longArray) + .addStatement("__db.assertNotSuspendingTransaction()") + .addStatement("__db.beginTransaction()") + .addCode(""" + |try { + | val _result = $adapterName.insertAndReturnIdsArray(items) + | __db.setTransactionSuccessful() + | return _result + |} finally { + | __db.endTransaction() + |} + """.trimMargin()) + .build() + + cls.addFunction(function) + cls.addFunction(functionAll) + return usedTypeConverters + } + + override fun getSupportedAnnotationTypes(): MutableSet { + return mutableSetOf(SelectiveDao::class.java.canonicalName) + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9edbc9cb..85e720be 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Apr 23 16:42:40 CEST 2019 +#Mon Aug 24 17:15:24 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/material-about-library/build.gradle b/material-about-library/build.gradle index 8ff2c558..c79c427c 100644 --- a/material-about-library/build.gradle +++ b/material-about-library/build.gradle @@ -8,11 +8,11 @@ def versionPatch = 0 def versionBuild = 0 // bump for dogfood builds, public betas, etc. android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk defaultConfig { minSdkVersion 14 - targetSdkVersion rootProject.ext.targetSdkVersion + targetSdkVersion setup.targetSdk versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionName "${versionMajor}.${versionMinor}" + (versionPatch == 0 ? "" : ".${versionPatch}") } @@ -32,7 +32,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "androidx.appcompat:appcompat:${androidXAppCompat}" - implementation "androidx.cardview:cardview:${androidXCardView}" - implementation "com.google.android.material:material:${googleMaterial}" + implementation "androidx.appcompat:appcompat:${versions.appcompat}" + implementation "androidx.cardview:cardview:${versions.cardview}" + implementation "com.google.android.material:material:${versions.material}" } diff --git a/mhttp/build.gradle b/mhttp/build.gradle index a7863eb6..a17c5b48 100644 --- a/mhttp/build.gradle +++ b/mhttp/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk defaultConfig { minSdkVersion 9 - targetSdkVersion rootProject.ext.targetSdkVersion + targetSdkVersion setup.targetSdk consumerProguardFiles 'proguard-rules.pro' versionCode 1 versionName PROJ_VERSION @@ -40,7 +40,7 @@ android { } dependencies { - compileOnly "androidx.appcompat:appcompat:${androidXAppCompat}" + compileOnly "androidx.appcompat:appcompat:${versions.appcompat}" compileOnly 'io.reactivex.rxjava2:rxjava:2.1.3' api 'com.squareup.okhttp3:okhttp:3.12.0' api 'com.google.code.gson:gson:2.8.5' diff --git a/mhttp/src/main/java/im/wangchao/mhttp/callback/FileCallbackHandler.java b/mhttp/src/main/java/im/wangchao/mhttp/callback/FileCallbackHandler.java index 174515ee..796ec1cf 100644 --- a/mhttp/src/main/java/im/wangchao/mhttp/callback/FileCallbackHandler.java +++ b/mhttp/src/main/java/im/wangchao/mhttp/callback/FileCallbackHandler.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import im.wangchao.mhttp.AbsCallbackHandler; import im.wangchao.mhttp.Accept; @@ -19,7 +20,7 @@ import okhttp3.internal.Util; *

    Time : 下午2:39.

    */ public class FileCallbackHandler extends AbsCallbackHandler { - final private File file; + private File file; final private static int BUFFER_SIZE = 4096; public FileCallbackHandler(Context context){ @@ -66,6 +67,18 @@ public class FileCallbackHandler extends AbsCallbackHandler { if (file == null){ throw new IllegalArgumentException("File == null"); } + if (this.file.isDirectory()) { + String contentDisposition = response.header("content-disposition"); + if (contentDisposition != null) { + if (contentDisposition.contains("*=UTF-8")) { + contentDisposition = contentDisposition.replace("*=UTF-8''", "\"") + "\""; + contentDisposition = URLDecoder.decode(contentDisposition, "UTF-8"); + } + String filename = contentDisposition.substring(contentDisposition.indexOf("\"") + 1, contentDisposition.lastIndexOf("\"")); + this.file = new File(file, filename); + file = this.file; + } + } InputStream instream = response.body().byteStream(); long contentLength = response.body().contentLength(); FileOutputStream buffer = new FileOutputStream(file); diff --git a/mhttp/src/main/java/im/wangchao/mhttp/internal/Version.java b/mhttp/src/main/java/im/wangchao/mhttp/internal/Version.java index 70b59853..847fe87e 100644 --- a/mhttp/src/main/java/im/wangchao/mhttp/internal/Version.java +++ b/mhttp/src/main/java/im/wangchao/mhttp/internal/Version.java @@ -1,7 +1,5 @@ package im.wangchao.mhttp.internal; -import im.wangchao.mhttp.BuildConfig; - /** *

    Description : Version.

    *

    Author : wangchao.

    @@ -14,7 +12,7 @@ public class Version { } public static String userAgent() { - return moduleName().concat(BuildConfig.VERSION_NAME); + return moduleName().concat("1.10.1"); } public static String moduleName() { diff --git a/nachos/build.gradle b/nachos/build.gradle index cd4d8235..821a4a83 100644 --- a/nachos/build.gradle +++ b/nachos/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion setup.compileSdk defaultConfig { minSdkVersion 15 - targetSdkVersion 28 + targetSdkVersion setup.targetSdk versionCode 1 versionName "1.0" } diff --git a/nachos/src/main/java/com/hootsuite/nachos/NachoTextView.java b/nachos/src/main/java/com/hootsuite/nachos/NachoTextView.java index 1a9e9be7..b2cb81ab 100644 --- a/nachos/src/main/java/com/hootsuite/nachos/NachoTextView.java +++ b/nachos/src/main/java/com/hootsuite/nachos/NachoTextView.java @@ -1078,6 +1078,22 @@ public class NachoTextView extends MultiAutoCompleteTextView implements TextWatc return chipAndTokenValues; } + public boolean ignoreThreshold = false; + + @Override + public boolean enoughToFilter() { + return ignoreThreshold || super.enoughToFilter(); + } + + public OnDismissListener onDismissListener = null; + + @Override + public void dismissDropDown() { + if (onDismissListener != null) + onDismissListener.onDismiss(); + super.dismissDropDown(); + } + @Override public String toString() { try { diff --git a/settings.gradle b/settings.gradle index 692dbba0..3625fac9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,6 @@ +include ':codegen' +include ':annotation' +rootProject.name='Szkolny.eu' include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos' /* include ':Navigation' diff --git a/szkolny-font/build.gradle b/szkolny-font/build.gradle index 3e136e8d..70d20b0d 100644 --- a/szkolny-font/build.gradle +++ b/szkolny-font/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2014 Mike Penz + * Copyright 2019 Mike Penz * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,14 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion + compileSdkVersion setup.compileSdk defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion setup.minSdk + targetSdkVersion setup.targetSdk consumerProguardFiles 'consumer-proguard-rules.pro' - versionCode 1 - versionName "1.0" + versionCode 11 + versionName "1.1" } buildTypes { release { @@ -46,6 +45,6 @@ if (project.hasProperty('pushall') || project.hasProperty('SzkolnyFontonly')) { } dependencies { - implementation "com.mikepenz:iconics-core:${iconics}" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "com.mikepenz:iconics-core:${versions.iconics}" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}" } diff --git a/szkolny-font/src/main/java/com/mikepenz/iconics/typeface/library/szkolny/font/SzkolnyFont.kt b/szkolny-font/src/main/java/com/mikepenz/iconics/typeface/library/szkolny/font/SzkolnyFont.kt index 593952fd..5e23dbd1 100644 --- a/szkolny-font/src/main/java/com/mikepenz/iconics/typeface/library/szkolny/font/SzkolnyFont.kt +++ b/szkolny-font/src/main/java/com/mikepenz/iconics/typeface/library/szkolny/font/SzkolnyFont.kt @@ -1,6 +1,5 @@ /* - * Copyright 2014 Mike Penz - * Copyright 2015 Haruki Hasegawa + * Copyright 2019 Mike Penz * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,21 +18,18 @@ package com.mikepenz.iconics.typeface.library.szkolny.font import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.ITypeface import com.mikepenz.iconics.typeface.library.szkolny.R -import java.util.LinkedList +import java.util.* @Suppress("EnumEntryName") object SzkolnyFont : ITypeface { override val fontRes: Int - get() = R.font.szkolny_font_font_v1_0 + get() = R.font.szkolny_font_font_v1_1 override val characters: Map by lazy { - mutableMapOf().apply { - SzkolnyFont.Icon.values().associateTo(this) { it.name to it.character } - //Icon2.values().associateTo(this) { it.name to it.character } - } + Icon.values().associate { it.name to it.character } } - + override val mappingPrefix: String get() = "szf" @@ -41,7 +37,7 @@ object SzkolnyFont : ITypeface { get() = "Szkolny Font" override val version: String - get() = "1.0" + get() = "1.1" override val iconCount: Int get() = characters.size @@ -64,17 +60,28 @@ object SzkolnyFont : ITypeface { override val licenseUrl: String get() = "" - override fun getIcon(key: String): IIcon { - return SzkolnyFont.Icon.valueOf(key) - } + override fun getIcon(key: String): IIcon = Icon.valueOf(key) enum class Icon constructor(override val character: Char) : IIcon { - szf_eye_check('\ue800'), - szf_calendar_off('\ue801'), - szf_file_document_edit('\ue802'), - szf_message_off('\ue803'), - szf_numeric_0_box_multiple_outline_off('\ue804'); + szf_alarm_bell_outline('\ue800'), + szf_calendar_plus_outline('\ue801'), + szf_calendar_today_outline('\ue802'), + szf_clipboard_list_outline('\ue803'), + szf_delete_empty_outline('\ue804'), + szf_discord_outline('\ue805'), + szf_file_code_outline('\ue806'), + szf_file_excel_outline('\ue807'), + szf_file_image_outline('\ue808'), + szf_file_music_outline('\ue809'), + szf_file_pdf_outline('\ue80a'), + szf_file_percent_outline('\ue80b'), + szf_file_powerpoint_outline('\ue80c'), + szf_file_video_outline('\ue80d'), + szf_file_word_outline('\ue80e'), + szf_message_processing_outline('\ue80f'), + szf_notebook_outline('\ue810'), + szf_zip_box_outline('\ue811'); override val typeface: ITypeface by lazy { SzkolnyFont } } -} +} \ No newline at end of file diff --git a/szkolny-font/src/main/res/font/szkolny_font_font_v1_0.ttf b/szkolny-font/src/main/res/font/szkolny_font_font_v1_0.ttf deleted file mode 100644 index 351575db..00000000 Binary files a/szkolny-font/src/main/res/font/szkolny_font_font_v1_0.ttf and /dev/null differ diff --git a/szkolny-font/src/main/res/font/szkolny_font_font_v1_1.ttf b/szkolny-font/src/main/res/font/szkolny_font_font_v1_1.ttf new file mode 100644 index 00000000..33ad627f Binary files /dev/null and b/szkolny-font/src/main/res/font/szkolny_font_font_v1_1.ttf differ