Compare commits

..

3 Commits

1221 changed files with 45570 additions and 62097 deletions

7
.gitignore vendored
View File

@ -81,10 +81,3 @@ lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
app/schemas/
signatures/
app/.cxx
/i18n/

1
.idea/.name generated
View File

@ -1 +0,0 @@
Szkolny.eu

View File

@ -1,10 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
@ -15,6 +11,7 @@
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -25,6 +22,7 @@
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -36,6 +34,7 @@
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -46,6 +45,7 @@
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -56,6 +56,7 @@
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -66,6 +67,7 @@
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -76,6 +78,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -87,6 +90,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -98,6 +102,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
@ -107,8 +112,5 @@
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

17
.idea/compiler.xml generated
View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.7">
<module name="annotation" target="1.7" />
<module name="codegen" target="1.7" />
<module name="Szkolny.eu.agendacalendarview" target="11" />
<module name="Szkolny.eu.app" target="11" />
<module name="Szkolny.eu.cafebar" target="11" />
<module name="Szkolny.eu.material-about-library" target="11" />
<module name="Szkolny.eu.mhttp" target="11" />
<module name="Szkolny.eu.nachos" target="11" />
<module name="Szkolny.eu.szkolny-font" target="11" />
<module name="Szkolny.eu.wear" target="11" />
</bytecodeTargetLevel>
</component>
</project>

2
.idea/discord.xml generated
View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="show" value="true" />
</component>
<component name="ProjectNotificationSettings">
<option name="askShowProject" value="false" />

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://kotlin.bintray.com/kotlinx/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://dl.bintray.com/wulkanowy/wulkanowy" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://dl.bintray.com/undervoid/Powerpermission" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://dl.bintray.com/undervoid/PowerPermission" />
</remote-repository>
</component>
</project>

7
.idea/misc.xml generated
View File

@ -6,9 +6,8 @@
</configurations>
</component>
<component name="EntryPointsManager">
<list size="2">
<item index="0" class="java.lang.String" itemvalue="androidx.databinding.BindingAdapter" />
<item index="1" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
<list size="1">
<item index="0" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
</list>
</component>
<component name="NullableNotNullManager">
@ -50,7 +49,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -3,7 +3,6 @@
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
//apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion setup.compileSdk
compileSdkVersion rootProject.ext.compileSdkVersion
android {
lintOptions {
@ -12,7 +12,7 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion setup.targetSdk
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
@ -43,9 +43,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Google libraries
implementation "androidx.appcompat:appcompat:${versions.appcompat}"
implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}"
implementation "com.google.android.material:material:${versions.material}"
implementation "androidx.appcompat:appcompat:${androidXAppCompat}"
implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}"
implementation "com.google.android.material:material:${googleMaterial}"
// other libraries
//implementation 'se.emilsjolander:stickylistheaders:2.7.0'

View File

@ -1 +0,0 @@
/build

View File

@ -1,29 +0,0 @@
/*
* 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"
}
}

View File

@ -1,14 +0,0 @@
/*
* 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<*>
)

View File

@ -1,13 +0,0 @@
/*
* 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<String>,
val skippedColumns: Array<String> = []
)

View File

@ -1,14 +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: 'com.google.firebase.crashlytics'
apply plugin: 'io.fabric'
android {
signingConfigs {
}
compileSdkVersion setup.compileSdk
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId 'pl.szczodrzynski.edziennik'
minSdkVersion setup.minSdk
@ -16,12 +16,6 @@ android {
versionCode release.versionCode
versionName release.versionName
multiDexEnabled true
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
buildTypes {
applicationVariants.all { variant ->
@ -54,11 +48,10 @@ android {
lintOptions {
checkReleaseBuilds false
}
buildFeatures {
dataBinding = true
dataBinding {
enabled = true
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility '1.8'
targetCompatibility '1.8'
}
@ -70,13 +63,6 @@ android {
packagingOptions {
exclude 'META-INF/library-core_release.kotlin_module'
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
ndkVersion '21.3.6528147'
}
/*task finalizeBundleDebug(type: Copy) {
@ -106,8 +92,6 @@ tasks.whenTaskAdded { task ->
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
kapt "androidx.room:room-compiler:${versions.room}"
debugImplementation "com.amitshekhar.android:debug-db:1.0.5"
@ -118,7 +102,7 @@ dependencies {
implementation "androidx.core:core-ktx:${versions.ktx}"
implementation "androidx.gridlayout:gridlayout:${versions.gridLayout}"
implementation "androidx.legacy:legacy-support-v4:${versions.legacy}"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycle}"
implementation "androidx.lifecycle:lifecycle-livedata:${versions.lifecycle}"
implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}"
implementation "androidx.room:room-runtime:${versions.room}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
@ -132,7 +116,6 @@ 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}"
@ -141,7 +124,7 @@ dependencies {
implementation "cat.ereza:customactivityoncrash:2.2.0"
implementation "com.applandeo:material-calendar-view:1.5.0"
implementation 'com.google.firebase:firebase-crashlytics:17.3.1'
implementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation "com.evernote:android-job:1.2.6"
implementation "com.github.antonKozyriatskyi:CircularProgressIndicator:1.2.2"
@ -149,11 +132,7 @@ 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") {
version {
strictly "3.12.13"
}
}
implementation "com.squareup.okhttp3:okhttp:3.12.0"
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"
@ -161,7 +140,7 @@ dependencies {
implementation "me.grantland:autofittextview:0.2.1"
implementation "me.leolin:ShortcutBadger:1.1.22@aar"
implementation "org.greenrobot:eventbus:3.1.1"
implementation "org.jsoup:jsoup:1.12.1"
implementation "org.jsoup:jsoup:1.10.1"
implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.15"
//implementation "se.emilsjolander:stickylistheaders:2.7.0"
implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT@aar'
@ -175,45 +154,22 @@ dependencies {
//implementation project(":Navigation")
implementation project(":szkolny-font")
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
//releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
debugImplementation "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'
//implementation "org.redundent:kotlin-xml-builder:1.5.3"
implementation "org.redundent:kotlin-xml-builder:1.5.3"
implementation 'com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31'
implementation "io.github.wulkanowy:signer-android:0.1.1"
implementation "androidx.work:work-runtime-ktx:${versions.work}"
implementation 'com.hypertrack:hyperlog:0.0.10'
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:2.0.0'
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"
api "com.google.dagger:dagger:${versions.dagger}"
api "com.google.dagger:dagger-android-support:${versions.dagger}"
kapt "com.google.dagger:dagger-compiler:${versions.dagger}"
kapt "com.google.dagger:dagger-android-processor:${versions.dagger}"
}
repositories {
mavenCentral()

Binary file not shown.

View File

@ -22,20 +22,12 @@
-keep class android.support.v7.widget.** { *; }
-keep class pl.szczodrzynski.edziennik.utils.models.** { *; }
-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 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 .R
-keep class **.R$* {
@ -47,26 +39,4 @@
-keep class okhttp3.** { *; }
-keep class com.google.android.material.tabs.** {*;}
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
-keepclasseswithmembernames class * {
native <methods>;
}
-keep class pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing { public final byte[] pleaseStopRightNow(java.lang.String, long); }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo$Platform { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData$Type { *; }
-keep class com.google.android.material.tabs.** {*;}

View File

@ -1,5 +0,0 @@
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF4caf50"
android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
</vector>

View File

@ -1,13 +0,0 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-12-28.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M13.5,15.5H10V12.5H13.5A1.5,1.5 0,0 1,15 14A1.5,1.5 0,0 1,13.5 15.5M10,6.5H13A1.5,1.5 0,0 1,14.5 8A1.5,1.5 0,0 1,13 9.5H10M15.6,10.79C16.57,10.11 17.25,9 17.25,8C17.25,5.74 15.5,4 13.25,4H7V18H14.04C16.14,18 17.75,16.3 17.75,14.21C17.75,12.69 16.89,11.39 15.6,10.79Z"/>
</vector>

View File

@ -1,13 +0,0 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-12-28.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M5,21H19V19H5V21M12,17A6,6 0,0 0,18 11V3H15.5V11A3.5,3.5 0,0 1,12 14.5A3.5,3.5 0,0 1,8.5 11V3H6V11A6,6 0,0 0,12 17Z"/>
</vector>

View File

@ -1,13 +0,0 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-25.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,8A4,4 0,0 1,16 12A4,4 0,0 1,12 16A4,4 0,0 1,8 12A4,4 0,0 1,12 8M12,10A2,2 0,0 0,10 12A2,2 0,0 0,12 14A2,2 0,0 0,14 12A2,2 0,0 0,12 10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -3,20 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="pl.szczodrzynski.edziennik">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- PowerPermission uses minSdk 21, it's safe to override as it is used only in >= 23 -->
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core" />
<application
android:name=".App"
android:allowBackup="true"
@ -24,22 +10,15 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme.Dark"
android:theme="@style/SplashTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<!-- __ __ _ _ _ _ _
| \/ | (_) /\ | | (_) (_) |
| \ / | __ _ _ _ __ / \ ___| |_ ___ ___| |_ _ _
| |\/| |/ _` | | '_ \ / /\ \ / __| __| \ \ / / | __| | | |
| | | | (_| | | | | | / ____ \ (__| |_| |\ V /| | |_| |_| |
|_| |_|\__,_|_|_| |_| /_/ \_\___|\__|_| \_/ |_|\__|\__, |
__/ |
|___/ -->
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize"
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/SplashTheme">
<intent-filter>
@ -49,7 +28,63 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeActivity"
android:configChanges="orientation|screenSize"
android:label="@string/messages_compose_title"
android:theme="@style/AppTheme.Black" />
<activity
android:name=".ui.modules.feedback.FeedbackActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name"
android:theme="@style/AppTheme" />
<activity
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" />
<activity
android:name=".ui.modules.intro.ChangelogIntroActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:theme="@style/Theme.Intro" />
<!--
______ _ _
| ____(_) | |
| |__ _ _ __ ___| |__ __ _ ___ ___
| __| | | '__/ _ \ '_ \ / _` / __|/ _ \
| | | | | | __/ |_) | (_| \__ \ __/
|_| |_|_| \___|_.__/ \__,_|___/\___/
-->
<activity
android:name=".ui.modules.base.CrashActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:process=":error_activity"
android:theme="@style/DeadTheme" />
<!--
_____ _ _ _ _ _
/ ____| | | | | (_) (_) |
| | _ __ __ _ ___| |__ __ _ ___| |_ ___ ___| |_ _ _
| | | '__/ _` / __| '_ \ / _` |/ __| __| \ \ / / | __| | | |
| |____| | | (_| \__ \ | | | | (_| | (__| |_| |\ V /| | |_| |_| |
\_____|_| \__,_|___/_| |_| \__,_|\___|\__|_| \_/ |_|\__|\__, |
__/ |
|___/
-->
<activity
android:name=".ui.modules.base.CrashGtfoActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/DeadTheme" />
<activity
android:name=".widgets.WidgetConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<!--
__ ___ _ _
\ \ / (_) | | | |
@ -58,37 +93,73 @@
\ /\ / | | (_| | (_| | __/ |_ \__ \
\/ \/ |_|\__,_|\__, |\___|\__||___/
__/ |
|___/
|_
-->
<activity android:name=".ui.widgets.WidgetConfigActivity"
<activity
android:name=".widgets.timetable.LessonDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.Dark.NoDisplay">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<!-- TIMETABLE -->
<receiver android:name=".ui.widgets.timetable.WidgetTimetableProvider"
android:theme="@style/AppTheme.NoDisplay" />
<activity
android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme" />
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<activity
android:name=".ui.modules.webpush.WebPushConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme.Dark" />
<activity
android:name=".ui.modules.home.CounterActivity"
android:theme="@style/AppTheme.Black" />
<activity android:name=".ui.modules.webpush.QrScannerActivity" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<!--
_____ _ _
| __ \ (_) | |
| |__) | __ _____ ___ __| | ___ _ __ ___
| ___/ '__/ _ \ \ / / |/ _` |/ _ \ '__/ __|
| | | | | (_) \ V /| | (_| | __/ | \__ \
|_| |_| \___/ \_/ |_|\__,_|\___|_| |___/
-->
<receiver
android:name=".WidgetTimetable"
android:label="@string/widget_timetable_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_timetable_info" />
</receiver>
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity android:name=".ui.widgets.LessonDialogActivity"
android:label=""
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.Dark.NoDisplay" />
<!-- NOTIFICATIONS -->
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
<!--
____ _ _
| _ \ | | (_)
| |_) | ___ ___ | |_ _ __ ___ ___ ___ ___ _____ _ __
| _ < / _ \ / _ \| __| | '__/ _ \/ __/ _ \ \ \ / / _ \ '__|
| |_) | (_) | (_) | |_ | | | __/ (_| __/ |\ V / __/ |
|____/ \___/ \___/ \__| |_| \___|\___\___|_| \_/ \_____|
-->
<receiver
android:name=".widgets.notifications.WidgetNotifications"
android:label="@string/widget_notifications_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -98,10 +169,9 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_notifications_info" />
</receiver>
<service android:name=".ui.widgets.notifications.WidgetNotificationsService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<!-- LUCKY NUMBER -->
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
<receiver
android:name=".widgets.luckynumber.WidgetLuckyNumber"
android:label="@string/widget_lucky_number_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -111,107 +181,67 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_lucky_number_info" />
</receiver>
<!-- _ _ _ _ _
/\ | | (_) (_) | (_)
/ \ ___| |_ ___ ___| |_ _ ___ ___
/ /\ \ / __| __| \ \ / / | __| |/ _ \/ __|
/ ____ \ (__| |_| |\ V /| | |_| | __/\__ \
/_/ \_\___|\__|_| \_/ |_|\__|_|\___||___/
-->
<activity android:name=".ui.modules.base.CrashActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:process=":error_activity"
android:theme="@style/DeadTheme" />
<activity android:name=".ui.modules.base.CrashGtfoActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/DeadTheme" />
<activity android:name=".ui.modules.intro.ChangelogIntroActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:theme="@style/Theme.Intro" />
<activity android:name=".ui.modules.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" />
<activity android:name=".ui.modules.home.CounterActivity"
android:theme="@style/AppTheme.Black" />
<activity android:name=".ui.modules.feedback.FeedbackActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.webpush.QrScannerActivity" />
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<!-- _____ _
| __ \ (_)
| |__) |___ ___ ___ ___ _____ _ __ ___
| _ // _ \/ __/ _ \ \ \ / / _ \ '__/ __|
| | \ \ __/ (_| __/ |\ V / __/ | \__ \
|_| \_\___|\___\___|_| \_/ \___|_| |___/
-->
<receiver android:name=".receivers.UserPresentReceiver"
<receiver
android:name=".receivers.UserPresentReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver android:name=".sync.UpdateDownloaderService$DownloadProgressReceiver">
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.SzkolnyReceiver"
<service
android:name=".sync.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver
android:name=".sync.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<service
android:name=".widgets.timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".widgets.notifications.WidgetNotificationsService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service android:name=".receivers.BootReceiver$NotificationActionService" />
<service android:name=".Notifier$GetDataRetryService" />
<receiver
android:name=".receivers.SzkolnyReceiver"
android:exported="true">
<intent-filter>
<action android:name="pl.szczodrzynski.edziennik.SZKOLNY_MAIN" />
</intent-filter>
</receiver>
<!-- _____ _
/ ____| (_)
| (___ ___ _ ____ ___ ___ ___ ___
\___ \ / _ \ '__\ \ / / |/ __/ _ \/ __|
____) | __/ | \ V /| | (_| __/\__ \
|_____/ \___|_| \_/ |_|\___\___||___/
-->
<!--<service android:name=".sync.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>-->
<service android:name=".data.api.ApiService" />
<service android:name=".data.firebase.MyFirebaseService"
android:exported="false">
<intent-filter android:priority="10000000">
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service android:name=".sync.UpdateDownloaderService" />
<!--
_____ _ _
| __ \ (_) | |
| |__) | __ _____ ___ __| | ___ _ __ ___
| ___/ '__/ _ \ \ / / |/ _` |/ _ \ '__/ __|
| | | | | (_) \ V /| | (_| | __/ | \__ \
|_| |_| \___/ \_/ |_|\__,_|\___|_| |___/
-->
<provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<service android:name=".api.v2.ApiService" />
</application>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>

View File

@ -1,8 +1,99 @@
<h3>Wersja 4.6.1, 2021-03-04</h3>
<html>
<head>
<style type="text/css">
* {
word-wrap: break-word;
}
body {
background-color: #{bg-color}; color: #{text-color};
}
a {
color: #{link-color};
}
a:active {
color: #{link-color-active};
}
ol {
list-style-position: inside;
padding-left: 0;
padding-right: 0;
}
li:not(:first-child) {
padding-top: 8px;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h3>Wersja 3.1.1, 2019-10-09</h3>
<ul>
<li>Przywrócono obsługę dziennika Librus Synergia.</li>
<li>Librus: poprawiona synchronizacja kategorii i kolorów ocen.</li>
<li>Zmieniony kolor dolnego paska w ciemnym motywie.</li>
<li>Zaktualizowany licznik czasu lekcji.</li>
</ul>
<br>
<br>
Dzięki za korzystanie ze Szkolnego!<br>
<i>&copy; Kuba Szczodrzyński, Kacper Ziubryniewicz 2021</i>
<h3>Wersja 3.1, 2019-09-29</h3>
<ul>
<li>Poprawiony interfejs zadań domowych.</li>
<li>Librus: wyświetlanie komentarzy ocen.</li>
<li>Librus: wyświetlanie nieobecności nauczycieli w Terminarzu.</li>
<li>Librus: usprawniona synchronizacja ocen.</li>
<li>Poprawki angielskiego tłumaczenia.</li>
</ul>
<h3>Wersja 3.0.3, 2019-09-26</h3>
<ul>
<li>Librus: poprawka kilku błędów synchronizacji.</li>
<li>Vulcan: prawidłowe oznaczanie wiadomości jako przeczytana.</li>
<li>Vulcan: poprawiona synchronizacja wiadomości i frekwencji.</li>
<li>Vulcan: poprawka błędów logowania.</li>
</ul>
<h3>Wersja 3.0.2, 2019-09-24</h3>
<ul>
<li>Librus: pobieranie Bieżących ocen opisowych.</li>
<li>Poprawki UI: kolor ikon paska statusu w jasnym motywie.</li>
<li>Poprawka braku skanera QR do przekazywania powiadomień.</li>
<li>Poprawka wyboru koloru i daty własnego wydarzenia, które crashowały aplikację.</li>
</ul>
<h3>Wersja 3.0.1, 2019-09-19</h3>
<ul>
<li>Librus: Poprawa błędu synchronizacji.</li>
<li>Poprawki UI związane z paskiem nawigacji.</li>
<li>Mobidziennik: Pobieranie ocen w niektórych przedmiotach.</li>
</ul>
<h3>Wersja 3.0, 2019-09-13</h3>
<ul>
<li><b>Nowy wygląd i sposób nawigacji</b> w całej aplikacji.</li>
<li>Menu nawigacji można teraz otworzyć przyciskiem na <b>dolnym pasku</b>. Pociągnięcie w górę tego paska wyświetla <b>menu kontekstowe</b> dotyczące danego widoku.</li>
<li>Założyliśmy serwer Discord! <a href="https://discord.gg/n9e8pWr">https://discord.gg/n9e8pWr</a></li>
<br>
<li>Librus: poprawka powielonych ogłoszeń szkolnych.</li>
<li>Naprawiłem błąd nieskończonej synchronizacji w Vulcanie.</li>
<li>Naprawiłem crash launchera przy dodaniu widgetu.</li>
<li>Naprawiłem częste crashe związane z widokiem kalendarza.</li>
<li>Nowe, ładniejsze (choć trochę) motywy kolorów.</li>
<li>Dużo drobnych poprawek UI i działania aplikacji.</li>
</ul>
<!--<i>
<h3>Plany na następne wersje:</h3>
<ul>
<li>Widget kalendarza ze sprawdzianami, ulepszenie widoku kalendarza w aplikacji</li>
<li>Wsparcie dla systemu Synergia w jednostkach samorządu terytorialnego - aplikacja Nasze Szkoły</li>
<li>Wsparcie dla Librusa w systemie Oświata w Radomiu</li>
<li>EduDziennik</li>
<li>Mobireg</li>
<li>Możliwość edycji planu lekcji</li>
</ul>
</i>-->
</body>

View File

@ -1,44 +0,0 @@
# 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} )

View File

@ -1,520 +0,0 @@
#include <stdlib.h>
#include <memory.h>
#include "aes.h"
#include <stdio.h>
#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];
}

View File

@ -1,27 +0,0 @@
#ifndef AES_H
#define AES_H
#include <stddef.h>
#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

View File

@ -1,55 +0,0 @@
#include "jni.h"
#include <stdlib.h>
#include <cstring>
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;
}

View File

@ -1,78 +0,0 @@
#include <jni.h>
#include <string>
#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] = {
0xdc, 0x61, 0x4d, 0x5b, 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;
}

View File

@ -0,0 +1,743 @@
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.multidex.MultiDex;
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.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 dagger.android.AndroidInjector;
import dagger.android.support.DaggerApplication;
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.api.Edziennik;
import pl.szczodrzynski.edziennik.data.api.Iuczniowie;
import pl.szczodrzynski.edziennik.data.api.Librus;
import pl.szczodrzynski.edziennik.data.api.Mobidziennik;
import pl.szczodrzynski.edziennik.data.api.Vulcan;
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.di.DaggerAppComponent;
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.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 DaggerApplication 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 Jakdojade apiJakdojade;
public Edziennik apiEdziennik;
public Mobidziennik apiMobidziennik;
public Iuczniowie apiIuczniowie;
public Librus apiLibrus;
public Vulcan apiVulcan;
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
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.factory().create(this);
}
@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);
apiEdziennik = new Edziennik(this);
//apiJakdojade = new Jakdojade(this);
apiMobidziennik = new Mobidziennik(this);
apiIuczniowie = new Iuczniowie(this);
apiLibrus = new Librus(this);
apiVulcan = new Vulcan(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<ConnectionSpec> 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) {
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<CapabilityInfo> 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<Node> 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<String, List<Integer>> 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<Integer> appProfileIds = gson.fromJson(appSharedPrefs.getString("profiles", ""), new TypeToken<List<Integer>>(){}.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<String,?> keys = appSharedPrefs.getAll();
for (Map.Entry<String,?> 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<String, JsonElement> 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<Integer> 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;
}
}
}

View File

@ -1,408 +0,0 @@
/*
* 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 debugMode = false
var devMode = 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 (devMode || 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
devMode = config.debugMode
MHttp.instance().customOkHttpClient(http)
if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) }
}
config.ui.language?.let {
setLanguage(it)
}
debugMode = BuildConfig.DEBUG
if (BuildConfig.DEBUG)
devMode = true
Signing.getCert(this)
launch {
withContext(Dispatchers.Default) {
config.migrate(this@App)
if (config.devModePassword != null)
checkDevModePassword()
devMode = debugMode || 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"
)
val pushVulcanHebeApp = 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:7e16404b9e5deaaa")
.build(),
"VulcanHebe"
)
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()
}
}
FirebaseInstanceId.getInstance(pushVulcanHebeApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got VulcanHebe token: $token")
if (token != config.sync.tokenVulcanHebe) {
config.sync.tokenVulcanHebe = token
config.sync.tokenVulcanHebeList = 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
}
}
}

View File

@ -1,20 +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
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()
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,352 @@
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<NotificationCompat.Action>());
} 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<pl.szczodrzynski.edziennik.utils.models.Notification> 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);
}
}
}

View File

@ -0,0 +1,450 @@
package pl.szczodrzynski.edziennik;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.SparseArray;
import android.view.View;
import android.widget.RemoteViews;
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
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.profiles.Profile;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.models.Week;
import pl.szczodrzynski.edziennik.widgets.WidgetConfig;
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDetailsActivity;
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService;
import static pl.szczodrzynski.edziennik.ExtensionsKt.filterOutArchived;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
public class WidgetTimetable extends AppWidgetProvider {
public static final String ACTION_SYNC_DATA = "ACTION_SYNC_DATA";
private static final String TAG = "WidgetTimetable";
private static int modeInt = 0;
public WidgetTimetable() {
// Start the worker thread
//HandlerThread sWorkerThread = new HandlerThread("WidgetTimetable-worker");
//sWorkerThread.start();
//Handler sWorkerQueue = new Handler(sWorkerThread.getLooper());
}
public static SparseArray<List<ItemWidgetTimetableModel>> timetables = null;
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_SYNC_DATA.equals(intent.getAction())) {
EdziennikTask.Companion.sync().enqueue(context);
}
super.onReceive(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, WidgetTimetable.class);
intent.setAction(action);
return getPendingSelfIntent(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, Intent intent) {
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
ComponentName thisWidget = new ComponentName(context, WidgetTimetable.class);
timetables = new SparseArray<>();
//timetables.clear();
App app = (App)context.getApplicationContext();
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
// There may be multiple widgets active, so update all of them
for (int appWidgetId : allWidgetIds) {
//d(TAG, "thr "+Thread.currentThread().getName());
WidgetConfig widgetConfig = app.appConfig.widgetTimetableConfigs.get(appWidgetId);
if (widgetConfig == null) {
widgetConfig = new WidgetConfig(app.profileFirstId());
app.appConfig.widgetTimetableConfigs.put(appWidgetId, widgetConfig);
app.appConfig.savePending = true;
}
RemoteViews views;
if (widgetConfig.bigStyle) {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark_big : R.layout.widget_timetable_big);
}
else {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark : R.layout.widget_timetable);
}
PorterDuff.Mode mode = PorterDuff.Mode.DST_IN;
/*if (widgetConfig.darkTheme) {
switch (modeInt) {
case 0:
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
case 1:
mode = PorterDuff.Mode.DST_ATOP;
d(TAG, "DST_ATOP");
break;
case 2:
mode = PorterDuff.Mode.DST_IN;
d(TAG, "DST_IN");
break;
case 3:
mode = PorterDuff.Mode.DST_OUT;
d(TAG, "DST_OUT");
break;
case 4:
mode = PorterDuff.Mode.DST_OVER;
d(TAG, "DST_OVER");
break;
case 5:
mode = PorterDuff.Mode.LIGHTEN;
d(TAG, "LIGHTEN");
break;
case 6:
mode = PorterDuff.Mode.MULTIPLY;
d(TAG, "MULTIPLY");
break;
case 7:
mode = PorterDuff.Mode.OVERLAY;
d(TAG, "OVERLAY");
break;
case 8:
mode = PorterDuff.Mode.SCREEN;
d(TAG, "SCREEN");
break;
case 9:
mode = PorterDuff.Mode.SRC_ATOP;
d(TAG, "SRC_ATOP");
break;
case 10:
mode = PorterDuff.Mode.SRC_IN;
d(TAG, "SRC_IN");
break;
case 11:
mode = PorterDuff.Mode.SRC_OUT;
d(TAG, "SRC_OUT");
break;
case 12:
mode = PorterDuff.Mode.SRC_OVER;
d(TAG, "SRC_OVER");
break;
case 13:
mode = PorterDuff.Mode.XOR;
d(TAG, "XOR");
break;
default:
modeInt = 0;
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
}
}*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// this code seems to crash the launcher on >= P
float transparency = widgetConfig.opacity; //0...1
long colorFilter = 0x01000000L * (long) (255f * transparency);
try {
final Method[] declaredMethods = Class.forName("android.widget.RemoteViews").getDeclaredMethods();
final int len = declaredMethods.length;
if (len > 0) {
for (int m = 0; m < len; m++) {
final Method method = declaredMethods[m];
if (method.getName().equals("setDrawableParameters")) {
method.setAccessible(true);
method.invoke(views, R.id.widgetTimetableListView, true, -1, (int) colorFilter, mode, -1);
method.invoke(views, R.id.widgetTimetableHeader, true, -1, (int) colorFilter, mode, -1);
break;
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
Intent refreshIntent = new Intent(context, WidgetTimetable.class);
refreshIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
PendingIntent pendingRefreshIntent = PendingIntent.getBroadcast(context,
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent);
views.setOnClickPendingIntent(R.id.widgetTimetableSync, WidgetTimetable.getPendingSelfIntent(context, ACTION_SYNC_DATA));
views.setImageViewBitmap(R.id.widgetTimetableRefresh, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
views.setImageViewBitmap(R.id.widgetTimetableSync, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
boolean unified = widgetConfig.profileId == -1;
List<Profile> profileList = new ArrayList<>();
if (unified) {
profileList = app.db.profileDao().getAllNow();
filterOutArchived(profileList);
}
else {
Profile profile = app.db.profileDao().getFullByIdNow(widgetConfig.profileId);
if (profile != null) {
profileList.add(profile);
}
}
//d(TAG, "Profiles: "+ Arrays.toString(profileList.toArray()));
if (profileList == null || profileList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist));
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
//Register profile;
long bellSyncDiffMillis = 0;
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000;
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier;
bellSyncDiffMillis *= -1;
}
List<ItemWidgetTimetableModel> lessonList = new ArrayList<>();
Time syncedNow = Time.fromMillis(Time.getNow().getInMillis() + bellSyncDiffMillis);
Date today = Date.getToday();
int openProfileId = -1;
Date displayingDate = null;
int displayingWeekDay = 0;
if (unified) {
views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.widget_timetable_title_unified));
}
else {
views.setTextViewText(R.id.widgetTimetableSubtitle, profileList.get(0).getName());
openProfileId = profileList.get(0).getId();
}
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(unified ? -1 : openProfileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today);
int scrollPos = 0;
for (Profile profile: profileList) {
Date profileDisplayingDate = HomeFragment.findDateWithLessons(profile.getId(), lessons, syncedNow, 1);
int profileDisplayingWeekDay = profileDisplayingDate.getWeekDay();
int dayDiff = Date.diffDays(profileDisplayingDate, Date.getToday());
//d(TAG, "For profile "+profile.name+" displayingDate is "+profileDisplayingDate.getStringY_m_d());
if (displayingDate == null || profileDisplayingDate.getValue() < displayingDate.getValue()) {
displayingDate = profileDisplayingDate;
displayingWeekDay = profileDisplayingWeekDay;
//d(TAG, "Setting as global dd");
if (dayDiff == 0) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_today_format, Week.getFullDayName(displayingWeekDay)));
} else if (dayDiff == 1) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(displayingWeekDay)));
} else {
views.setTextViewText(R.id.widgetTimetableTitle, Week.getFullDayName(displayingWeekDay) + " " + profileDisplayingDate.getStringDm());
}
}
}
for (Profile profile: profileList) {
int pos = 0;
List<EventFull> events = app.db.eventDao().getAllByDateNow(profile.getId(), displayingDate);
if (events == null)
events = new ArrayList<>();
if (unified) {
ItemWidgetTimetableModel separator = new ItemWidgetTimetableModel();
separator.profileId = profile.getId();
separator.bigStyle = widgetConfig.bigStyle;
separator.darkTheme = widgetConfig.darkTheme;
separator.separatorProfileName = profile.getName();
lessonList.add(separator);
}
for (LessonFull lesson : lessons) {
//d(TAG, "Profile "+profile.id+" Lesson profileId "+lesson.profileId+" weekDay "+lesson.weekDay+", "+lesson);
if (profile.getId() != lesson.profileId || displayingWeekDay != lesson.weekDay)
continue;
//d(TAG, "Not skipped");
ItemWidgetTimetableModel model = new ItemWidgetTimetableModel();
model.bigStyle = widgetConfig.bigStyle;
model.darkTheme = widgetConfig.darkTheme;
model.profileId = profile.getId();
model.lessonDate = displayingDate;
model.startTime = lesson.startTime;
model.endTime = lesson.endTime;
model.lessonPassed = (syncedNow.getValue() > lesson.endTime.getValue()) && displayingWeekDay == Week.getTodayWeekDay();
model.lessonCurrent = (Time.inRange(lesson.startTime, lesson.endTime, syncedNow)) && displayingWeekDay == Week.getTodayWeekDay();
if (model.lessonCurrent) {
scrollPos = pos;
} else if (model.lessonPassed) {
scrollPos = pos + 1;
}
pos++;
model.subjectName = bs(lesson.subjectLongName);
model.classroomName = lesson.classroomName;
model.bellSyncDiffMillis = bellSyncDiffMillis;
if (lesson.changeId != 0) {
if (lesson.changeType == LessonChange.TYPE_CHANGE) {
model.lessonChange = true;
if (lesson.changedClassroomName()) {
model.newClassroomName = lesson.changeClassroomName;
}
if (lesson.changedSubjectLongName()) {
model.newSubjectName = lesson.changeSubjectLongName;
}
}
if (lesson.changeType == LessonChange.TYPE_CANCELLED) {
model.lessonCancelled = true;
}
}
for (EventFull event : events) {
if (event.startTime == null)
continue;
if (event.eventDate.getValue() == displayingDate.getValue()
&& event.startTime.getValue() == lesson.startTime.getValue()) {
model.eventColors.add(event.type == TYPE_HOMEWORK ? ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK : event.getColor());
}
}
lessonList.add(model);
}
}
if (lessonList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
timetables.put(appWidgetId, lessonList);
//WidgetTimetableListProvider.widgetsLessons.put(appWidgetId, lessons);
//views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
Intent listIntent = new Intent(context, WidgetTimetableService.class);
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent);
// template to handle the click listener for each item
Intent intentTemplate = new Intent(context, LessonDetailsActivity.class);
// Old activities shouldn't be in the history stack
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntentTimetable = PendingIntent.getActivity(context,
0,
intentTemplate,
0);
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable);
Intent openIntent = new Intent(context, MainActivity.class);
openIntent.setAction("android.intent.action.MAIN");
if (!unified) {
openIntent.putExtra("profileId", openProfileId);
openIntent.putExtra("timetableDate", displayingDate.getValue());
}
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE);
PendingIntent pendingOpenIntent = PendingIntent.getActivity(context,
appWidgetId, openIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent);
if (!unified)
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos);
}
}
appWidgetManager.updateAppWidget(appWidgetId, views);
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView);
}
//modeInt++;
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
App app = (App) context.getApplicationContext();
for (int appWidgetId: appWidgetIds) {
app.appConfig.widgetTimetableConfigs.remove(appWidgetId);
}
app.saveConfig("widgetTimetableConfigs");
}
}

View File

@ -2,7 +2,7 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.data.api
package pl.szczodrzynski.edziennik.api.v2
import android.app.Service
import android.content.Context
@ -12,17 +12,15 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
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.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.utils.Utils.d
import kotlin.math.min
import kotlin.math.roundToInt
@ -30,7 +28,7 @@ import kotlin.math.roundToInt
class ApiService : Service() {
companion object {
const val TAG = "ApiService"
const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.SYNC"
const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.GET_DATA"
fun start(context: Context) {
context.startService(Intent(context, ApiService::class.java))
}
@ -38,34 +36,29 @@ 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 syncingProfiles = mutableListOf<Profile>()
private var szkolnyTaskFinished = false
private val allTaskRequestList = mutableListOf<Any>()
private val finishingTaskQueue = mutableListOf(
NotifyTask(),
ErrorReportTask()
)
private val taskQueue = mutableListOf<IApiTask>()
private val errorList = mutableListOf<ApiError>()
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(app) }
private val notification by lazy { EdziennikNotification(this) }
/* ______ _ _ _ _ _____ _ _ _ _
| ____| | | (_) (_) | / ____| | | | | | |
@ -75,33 +68,32 @@ class ApiService : Service() {
|______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/
private val taskCallback = object : EdziennikCallback {
override fun onCompleted() {
lastEventTime = System.currentTimeMillis()
d(TAG, "Task $taskRunningId (profile $taskProfileId) finished in ${System.currentTimeMillis()-taskStartTime}")
EventBus.getDefault().postSticky(ApiTaskFinishedEvent(taskProfileId))
clearTask()
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
notification.setIdle().post()
runTask()
}
override fun onError(apiError: ApiError) {
lastEventTime = System.currentTimeMillis()
d(TAG, "Task $taskRunningId threw an error - $apiError")
apiError.profileId = taskProfileId
if (app.userActionManager.requiresUserAction(apiError)) {
app.userActionManager.sendToUser(apiError)
}
else {
EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError))
errorList.add(apiError)
apiError.throwable?.printStackTrace()
}
EventBus.getDefault().post(ApiTaskErrorEvent(apiError))
errorList.add(apiError)
apiError.throwable?.printStackTrace()
if (apiError.isCritical) {
taskRunning?.cancel()
notification.setCriticalError().post()
clearTask()
taskRunning = null
taskIsRunning = false
taskRunningId = -1
runTask()
}
else {
@ -110,7 +102,6 @@ class ApiService : Service() {
}
override fun onProgress(step: Float) {
lastEventTime = System.currentTimeMillis()
if (step <= 0)
return
if (taskProgress < 0)
@ -123,7 +114,6 @@ 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))
@ -138,23 +128,15 @@ class ApiService : Service() {
| | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | |
|_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/
private fun runTask() {
checkIfTaskFrozen()
if (taskIsRunning)
return
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && szkolnyTaskFinished)) {
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
serviceClosed = false
allCompleted()
return
}
lastEventTime = System.currentTimeMillis()
val task = if (taskQueue.isNotEmpty()) {
taskQueue.removeAt(0)
} else {
szkolnyTaskFinished = true
SzkolnyTask(app, syncingProfiles)
}
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
task.taskId = ++taskMaximumId
task.prepare(app)
taskIsRunning = true
@ -164,7 +146,7 @@ class ApiService : Service() {
taskProgress = -1f
taskProgressText = task.taskName
d(TAG, "Executing task $taskRunningId - ${task::class.java.name}")
d(TAG, "Executing task $taskRunningId ($taskProgressText) - $task")
// update the notification
notification.setCurrentTask(taskRunningId, taskProgressText).post()
@ -172,65 +154,15 @@ 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 ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
is SzkolnyTask -> task.run(taskCallback)
}
} catch (e: Exception) {
taskCallback.onError(e.toApiError(TAG))
when (task) {
is EdziennikTask -> task.run(app, taskCallback)
is NotifyTask -> task.run(app, taskCallback)
is ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
}
}
/**
* 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() {
serviceClosed = true
EventBus.getDefault().postSticky(ApiTaskAllFinishedEvent())
EventBus.getDefault().post(ApiTaskAllFinishedEvent())
stopSelf()
}
@ -245,13 +177,6 @@ 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 {
@ -281,10 +206,8 @@ 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) {
@ -294,7 +217,7 @@ class ApiService : Service() {
serviceClosed = true
taskCancelled = true
taskRunning?.cancel()
allCompleted()
stopSelf()
}
/* _____ _ _ _
@ -304,20 +227,16 @@ class ApiService : Service() {
____) | __/ | \ V /| | (_| __/ | (_) \ V / __/ | | | | | (_| | __/\__ \
|_____/ \___|_| \_/ |_|\___\___| \___/ \_/ \___|_| |_| |_|\__,_|\___||__*/
override fun onCreate() {
d(TAG, "Service created")
EventBus.getDefault().register(this)
notification.setIdle().setCloseAction()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
d(TAG, "Foreground service onStartCommand")
startForeground(app.notificationChannelsManager.sync.id, notification.notification)
startForeground(EdziennikNotification.NOTIFICATION_ID, notification.notification)
return START_NOT_STICKY
}
override fun onDestroy() {
d(TAG, "Service destroyed")
serviceClosed = true
EventBus.getDefault().unregister(this)
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-19.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.os.Build
import pl.szczodrzynski.edziennik.BuildConfig
const val GET = 0
const val POST = 1
val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Android"
val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT"
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_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"
/** https://api.librus.pl/2.0 */
const val LIBRUS_API_URL = "https://api.librus.pl/2.0"
/** https://portal.librus.pl/api */
const val LIBRUS_PORTAL_URL = "https://portal.librus.pl/api"
/** https://api.librus.pl/OAuth/Token */
const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
/** https://api.librus.pl/OAuth/TokenJST */
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21"
const val LIBRUS_JST_DEMO_PIN = "1290"
const val LIBRUS_SYNERGIA_URL = "https://synergia.librus.pl"
/** https://synergia.librus.pl/loguj/token/TOKEN/przenies */
const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/TOKEN/przenies"
const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module"
const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="
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_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_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"
val IDZIENNIK_API_USER_AGENT = SYSTEM_USER_AGENT
const val IDZIENNIK_API_URL = "https://iuczniowie.progman.pl/idziennik/api"
const val IDZIENNIK_API_CURRENT_REGISTER = "Uczniowie/\$STUDENT_ID/AktualnyDziennik"
const val IDZIENNIK_API_GRADES = "Uczniowie/\$STUDENT_ID/Oceny/" /* + semester */
const val IDZIENNIK_API_MESSAGES_INBOX = "Wiadomosci/Odebrane"
const val IDZIENNIK_API_MESSAGES_SENT = "Wiadomosci/Wyslane"
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_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06"
const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB"
val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}"
const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat"
const val VULCAN_API_ENDPOINT_STUDENT_LIST = "mobile-api/Uczen.v3.UczenStart/ListaUczniow"
const val VULCAN_API_ENDPOINT_DICTIONARIES = "mobile-api/Uczen.v3.Uczen/Slowniki"
const val VULCAN_API_ENDPOINT_TIMETABLE = "mobile-api/Uczen.v3.Uczen/PlanLekcjiZeZmianami"
const val VULCAN_API_ENDPOINT_GRADES = "mobile-api/Uczen.v3.Uczen/Oceny"
const val VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS = "mobile-api/Uczen.v3.Uczen/OcenyPodsumowanie"
const val VULCAN_API_ENDPOINT_EVENTS = "mobile-api/Uczen.v3.Uczen/Sprawdziany"
const val VULCAN_API_ENDPOINT_HOMEWORK = "mobile-api/Uczen.v3.Uczen/ZadaniaDomowe"
const val VULCAN_API_ENDPOINT_NOTICES = "mobile-api/Uczen.v3.Uczen/UwagiUcznia"
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_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"

View File

@ -0,0 +1,210 @@
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)
}}
}

View File

@ -2,26 +2,28 @@
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.data.api
package pl.szczodrzynski.edziennik.api.v2
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 app: App) {
private val notificationManager by lazy { app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
class EdziennikNotification(val context: Context) {
companion object {
const val NOTIFICATION_ID = 20191001
}
private val notificationManager by lazy { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
private val notificationBuilder: NotificationCompat.Builder by lazy {
NotificationCompat.Builder(app, ApiService.NOTIFICATION_API_CHANNEL_ID)
NotificationCompat.Builder(context, ApiService.NOTIFICATION_API_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setPriority(PRIORITY_MIN)
.setOngoing(true)
@ -33,42 +35,39 @@ class EdziennikNotification(val app: App) {
private var errorCount = 0
private var criticalErrorCount = 0
var serviceClosed = false
private fun cancelPendingIntent(taskId: Int): 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
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
}
private val closePendingIntent: PendingIntent
get() {
val intent = SzkolnyReceiver.getIntent(app, Bundle(
"task" to "ServiceCloseRequest"
))
return PendingIntent.getBroadcast(app, 0, intent, 0) as PendingIntent
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
intent.putExtra("task", "ServiceCloseRequest")
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent
}
private fun errorCountText(): String? {
var result = ""
if (criticalErrorCount > 0) {
result += app.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount)
result += context.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount)
}
if (criticalErrorCount > 0 && errorCount > 0) {
result += ", "
}
if (errorCount > 0) {
result += app.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount)
result += context.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount)
}
return if (result.isEmpty()) null else result
}
fun setIdle(): EdziennikNotification {
notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_title))
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = app.getString(R.string.edziennik_notification_api_text)
val str = context.getString(R.string.edziennik_notification_api_text)
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
@ -82,7 +81,7 @@ class EdziennikNotification(val app: App) {
}
fun setCriticalError(): EdziennikNotification {
criticalErrorCount++
notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_error_title))
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_error_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = errorCountText()
@ -119,7 +118,7 @@ class EdziennikNotification(val app: App) {
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
app.getString(R.string.edziennik_notification_api_close),
context.getString(R.string.edziennik_notification_api_close),
closePendingIntent
))
return this
@ -129,15 +128,13 @@ class EdziennikNotification(val app: App) {
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
app.getString(R.string.edziennik_notification_api_cancel),
context.getString(R.string.edziennik_notification_api_cancel),
cancelPendingIntent(taskId)
))
}
fun post() {
if (serviceClosed)
return
notificationManager.notify(app.notificationChannelsManager.sync.id, notification)
notificationManager.notify(NOTIFICATION_ID, notification)
}
}
}

View File

@ -1,13 +1,13 @@
package pl.szczodrzynski.edziennik.data.api
package pl.szczodrzynski.edziennik.api.v2
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
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
fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?) {
fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featureIds: List<Int>, viewId: Int?) {
val data = this
val possibleLoginMethods = data.loginMethods.toMutableList()
@ -46,19 +46,13 @@ fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, 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 (
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
if (timer.nextSync == SYNC_ALWAYS ||
(viewId != null && timer.viewId == viewId) ||
(timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp)) {
data.targetEndpointIds.add(endpoint.first)
requiredLoginMethods.add(endpoint.second)
}
}
@ -81,37 +75,9 @@ fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, 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()
}
fun Data.prepareFor(loginMethods: List<LoginMethod>, 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
}
}

View File

@ -2,7 +2,7 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.data.api
package pl.szczodrzynski.edziennik.api.v2
/*const val CODE_OTHER = 0
const val CODE_OK = 1
@ -31,11 +31,6 @@ 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
@ -43,27 +38,17 @@ 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_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_REQUEST_HTTP_500 = 57
const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_LOGIN_DATA_INVALID = 102
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
@ -114,20 +99,6 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
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
@ -138,73 +109,56 @@ const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210
const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 212
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
const val ERROR_LOGIN_VULCAN_INVALID_PIN = 309
const val ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING = 310
const val ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING = 311
const val ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING = 312
const val ERROR_LOGIN_VULCAN_EXPIRED_TOKEN = 321
const val ERROR_LOGIN_VULCAN_OTHER = 322
const val ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN = 330
const val ERROR_LOGIN_VULCAN_NO_PUPILS = 331
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_VULCAN_HEBE_OTHER = 354
const val ERROR_VULCAN_HEBE_SIGNATURE_ERROR = 360
const val ERROR_VULCAN_HEBE_INVALID_PAYLOAD = 361
const val ERROR_VULCAN_HEBE_FIREBASE_ERROR = 362
const val ERROR_VULCAN_HEBE_CERTIFICATE_GONE = 363
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
const val ERROR_VULCAN_API_DEPRECATED = 390
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_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_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
const val ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 403
const val ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE = 404
const val ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR = 405
const val ERROR_LOGIN_IDZIENNIK_WEB_OTHER = 410
const val ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS = 411 /* {"d":{"__type":"mds.Web.mod_komunikator.WS_mod_wiadomosci+detailWiadomosci","Wiadomosc":{"_recordId":0,"DataNadania":null,"DataOdczytania":null,"Nadawca":null,"ListaOdbiorcow":[],"Tytul":null,"Text":null,"ListaZal":[]},"Bledy":{"__type":"mds.Module.Globalne+sBledy","CzyJestBlad":true,"ListaBledow":["Nie masz dostępu do tych zasobów!"],"ListaKodowBledow":[]},"czyJestWiecej":false}} */
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION = 420
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH = 421
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER = 422
const val ERROR_IDZIENNIK_WEB_ACCESS_DENIED = 430
const val ERROR_IDZIENNIK_WEB_OTHER = 431
const val ERROR_IDZIENNIK_WEB_MAINTENANCE = 432
const val ERROR_IDZIENNIK_WEB_SERVER_ERROR = 433
const val ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 434
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_TEMPLATE_WEB_OTHER = 801
const val EXCEPTION_API_TASK = 900
const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901
const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902
const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903
const val EXCEPTION_LIBRUS_API_REQUEST = 904
const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905
const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906
const val EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST = 908
const val EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST = 909
const val EXCEPTION_NOTIFY = 910
const val EXCEPTION_VULCAN_API_REQUEST = 907
const val EXCEPTION_NOTIFY_AND_SYNC = 910
const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
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 EXCEPTION_VULCAN_HEBE_REQUEST = 950
const val LOGIN_NO_ARGUMENTS = 1201
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914

View File

@ -2,46 +2,48 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.data.api
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_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
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.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
internal const val FEATURE_TIMETABLE = 1
internal const val FEATURE_AGENDA = 2
internal const val FEATURE_GRADES = 3
internal const val FEATURE_HOMEWORK = 4
internal const val FEATURE_BEHAVIOUR = 5
internal const val FEATURE_ATTENDANCE = 6
internal const val FEATURE_MESSAGES_INBOX = 7
internal const val FEATURE_MESSAGES_SENT = 8
internal const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_ALL = 0
const val FEATURE_TIMETABLE = 1
const val FEATURE_AGENDA = 2
const val FEATURE_GRADES = 3
const val FEATURE_HOMEWORK = 4
const val FEATURE_BEHAVIOUR = 5
const val FEATURE_ATTENDANCE = 6
const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_SENT = 8
const val FEATURE_ANNOUNCEMENTS = 9
internal const val FEATURE_ALWAYS_NEEDED = 100
internal const val FEATURE_STUDENT_INFO = 101
internal const val FEATURE_STUDENT_NUMBER = 109
internal const val FEATURE_SCHOOL_INFO = 102
internal const val FEATURE_CLASS_INFO = 103
internal const val FEATURE_TEAM_INFO = 104
internal const val FEATURE_LUCKY_NUMBER = 105
internal const val FEATURE_TEACHERS = 106
internal const val FEATURE_SUBJECTS = 107
internal const val FEATURE_CLASSROOMS = 108
internal const val FEATURE_PUSH_CONFIG = 120
const val FEATURE_ALWAYS_NEEDED = 100
const val FEATURE_STUDENT_INFO = 101
const val FEATURE_STUDENT_NUMBER = 109
const val FEATURE_SCHOOL_INFO = 102
const val FEATURE_CLASS_INFO = 103
const val FEATURE_TEAM_INFO = 104
const val FEATURE_LUCKY_NUMBER = 105
const val FEATURE_TEACHERS = 106
const val FEATURE_SUBJECTS = 107
const val FEATURE_CLASSROOMS = 108
const val FEATURE_PUSH_CONFIG = 120
const val FEATURE_MESSAGE_GET = 201
object Features {
private fun getAllNecessary(): List<Int> = listOf(
FEATURE_ALWAYS_NEEDED,
FEATURE_PUSH_CONFIG,
FEATURE_STUDENT_INFO,
FEATURE_STUDENT_NUMBER,
FEATURE_SCHOOL_INFO,
@ -83,4 +85,4 @@ object Features {
else -> getAllFeatures()
} + getAllNecessary()).sorted()
}
}
}

View File

@ -2,29 +2,28 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.data.api
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb
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.VulcanLoginHebe
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
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
// librus
// mobidziennik
// idziennik [*]
// idziennik
// vulcan
// mobireg
const val SYNERGIA_API_ENABLED = false
const val SYNERGIA_API_ENABLED = true
const val LOGIN_TYPE_IDZIENNIK = 3
@ -32,10 +31,14 @@ const val LOGIN_TYPE_IDZIENNIK = 3
const val LOGIN_TYPE_TEMPLATE = 21
// LOGIN MODES
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_TEMPLATE_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_IDZIENNIK_WEB = 100
const val LOGIN_METHOD_IDZIENNIK_API = 200
const val LOGIN_METHOD_TEMPLATE_WEB = 100
const val LOGIN_METHOD_TEMPLATE_API = 200
@ -63,15 +66,15 @@ val librusLoginMethods = listOf(
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
if (profile?.hasStudentData("accountPassword") == false) 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") }
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
)
@ -82,28 +85,27 @@ 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 { profile, _ -> profile?.getStudentData("email", null) != null }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
.withIsPossible { _, loginStore -> loginStore.hasLoginData("email") }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }*/
)
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_MODE_VULCAN_API = 0
const val LOGIN_MODE_VULCAN_WEB = 1
const val LOGIN_MODE_VULCAN_HEBE = 2
const val LOGIN_METHOD_VULCAN_WEB_MAIN = 100
const val LOGIN_METHOD_VULCAN_WEB_NEW = 200
const val LOGIN_METHOD_VULCAN_WEB_OLD = 300
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
const val LOGIN_METHOD_VULCAN_HEBE = 600
const val LOGIN_METHOD_VULCAN_API = 500
val vulcanLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
.withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") }
/*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
.withIsPossible { _, _ -> false }
.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 },
@ -111,29 +113,21 @@ val vulcanLoginMethods = listOf(
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
.withIsPossible { _, loginStore ->
loginStore.mode != LOGIN_MODE_VULCAN_API
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
}
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
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)
val idziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_WEB, IdziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
.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)
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_API, IdziennikLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB }
)
val templateLoginMethods = listOf(
@ -144,4 +138,4 @@ val templateLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB }
)
)

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-6.
*/
package pl.szczodrzynski.edziennik.api.v2
object Regexes {
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
"""<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".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 {
""">&nbsp;(.+?):</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy {
"""Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy {
"""Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy {
"""Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_DETAILS by lazy {
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?<small>\((.+?)\)</small>.*?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>""".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]+)*</a>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_CLASS_CALENDAR by lazy {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_ERROR by lazy {
"""id="spanErrorMessage">(.*?)</""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME by lazy {
"""Imię i nazwisko:.+?">(.+?)</div>""".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 {
"""<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
}

View File

@ -0,0 +1,179 @@
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()
}}
}

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskAllFinishedEvent

View File

@ -0,0 +1,9 @@
/*
* 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)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskFinishedEvent(val profileId: Int)

View File

@ -2,6 +2,6 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.data.api.events
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskProgressEvent(val profileId: Int, val progress: Float, val progressText: String?)
class ApiTaskProgressEvent(val profileId: Int, val progress: Float, val progressText: String?)

View File

@ -0,0 +1,9 @@
/*
* 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)

View File

@ -0,0 +1,6 @@
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<Profile>, val loginStore: LoginStore)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class ServiceCloseRequest

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class TaskCancelRequest(val taskId: Int)

View File

@ -0,0 +1,90 @@
package pl.szczodrzynski.edziennik.api.v2.events.task
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
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<Pair<Int, Int>>? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds))
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
fun messageGet(profileId: Int, messageId: Int) = EdziennikTask(profileId, MessageGetRequest(messageId))
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 || !profile.syncEnabled) {
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
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)
is MessageGetRequest -> edziennikInterface?.getMessage(request.messageId)
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<Pair<Int, Int>>? = null)
data class SyncProfileListRequest(val profileList: List<Int>)
data class MessageGetRequest(val messageId: Int)
class AnnouncementsReadRequest
}

View File

@ -2,14 +2,14 @@
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.data.api.task
package pl.szczodrzynski.edziennik.api.v2.events.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ApiService
import pl.szczodrzynski.edziennik.data.api.EdziennikNotification
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.ApiService
import pl.szczodrzynski.edziennik.api.v2.EdziennikNotification
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils
class ErrorReportTask : IApiTask(-1) {
@ -31,4 +31,4 @@ class ErrorReportTask : IApiTask(-1) {
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events.task
import android.content.Context
import android.content.Intent
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.ApiService
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
abstract class IApiTask(open val profileId: Int) {
var taskId: Int = 0
var profile: Profile? = null
var taskName: String? = null
/**
* A method called before running the task.
* It is synchronous and its main task is
* to prepare the correct task name.
*/
abstract fun prepare(app: App)
abstract fun cancel()
fun enqueue(context: Context) {
context.startService(Intent(context, ApiService::class.java))
EventBus.getDefault().postSticky(this)
}
override fun toString(): String {
return "IApiTask(profileId=$profileId, taskId=$taskId, profile=$profile, taskName=$taskName)"
}
}

View File

@ -0,0 +1,89 @@
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()
}
}

View File

@ -0,0 +1,176 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.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
class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty()
fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
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()
))
}
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_IDZIENNIK_API
}
private var mLoginExpiryTime: Long? = null
var loginExpiryTime: Long
get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("loginExpiryTime", value); mLoginExpiryTime = value }
private var mApiExpiryTime: Long? = null
var apiExpiryTime: Long
get() { mApiExpiryTime = mApiExpiryTime ?: loginStore.getLoginData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("apiExpiryTime", value); mApiExpiryTime = value }
/* __ __ _
\ \ / / | |
\ \ /\ / /__| |__
\ \/ \/ / _ \ '_ \
\ /\ / __/ |_) |
\/ \/ \___|_._*/
private var mWebSchoolName: String? = null
var webSchoolName: String?
get() { mWebSchoolName = mWebSchoolName ?: loginStore.getLoginData("schoolName", null); return mWebSchoolName }
set(value) { loginStore.putLoginData("schoolName", value); mWebSchoolName = value }
private var mWebUsername: String? = null
var webUsername: String?
get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("username", null); return mWebUsername }
set(value) { loginStore.putLoginData("username", value); mWebUsername = value }
private var mWebPassword: String? = null
var webPassword: String?
get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("password", null); return mWebPassword }
set(value) { loginStore.putLoginData("password", value); mWebPassword = 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 mWebAuth: String? = null
var webAuth: String?
get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth }
set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value }
/* _
/\ (_)
/ \ _ __ _
/ /\ \ | '_ \| |
/ ____ \| |_) | |
/_/ \_\ .__/|_|
| |
|*/
private var mApiBearer: String? = null
var apiBearer: String?
get() { mApiBearer = mApiBearer ?: loginStore.getLoginData("apiBearer", null); return mApiBearer }
set(value) { loginStore.putLoginData("apiBearer", value); mApiBearer = 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 mRegisterId: Int? = null
var registerId: Int
get() { mRegisterId = mRegisterId ?: profile?.getStudentData("registerId", 0); return mRegisterId ?: 0 }
set(value) { profile?.putStudentData("registerId", value) ?: return; mRegisterId = value }
private var mSchoolYearId: Int? = null
var schoolYearId: Int
get() { mSchoolYearId = mSchoolYearId ?: profile?.getStudentData("schoolYearId", 0); return mSchoolYearId ?: 0 }
set(value) { profile?.putStudentData("schoolYearId", value) ?: return; mSchoolYearId = value }
/* _ _ _ _ _
| | | | | (_) |
| | | | |_ _| |___
| | | | __| | / __|
| |__| | |_| | \__ \
\____/ \__|_|_|__*/
fun getSubject(name: String, id: Long?, shortName: String): Subject {
var subject = if (id == null)
subjectList.singleOrNull { it.longName == name }
else
subjectList.singleOrNull { it.id == id }
if (subject == null) {
subject = Subject(profileId, id ?: name.crc16().toLong(), name, shortName)
subjectList[subject.id] = subject
}
return subject
}
fun getTeacher(firstName: String, lastName: String): Teacher {
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])
}
fun getTeacherByFirstLast(nameFirstLast: String): Teacher {
val nameParts = nameFirstLast.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0], nameParts[1])
}
fun getTeacherByFDotLast(nameFDotLast: String): Teacher {
val nameParts = nameFDotLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String): Teacher {
val nameParts = nameFDotSpaceLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String): Teacher {
(teacher ?: Teacher(profileId, -1, firstName, lastName).apply {
id = shortName.crc16().toLong()
teacherList[id] = this
}).apply {
if (firstName.length > 1)
name = firstName
surname = lastName
return this
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik
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.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<Int>()
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<Int>, viewId: Int?) {
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(messageId: Int) {
}
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)
}
}
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.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_NOTICES = 1070
const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080
const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX = 1110
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT = 1120
const val ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER = 2010
const val ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX = 2110
const val ENDPOINT_IDZIENNIK_API_MESSAGES_SENT = 2120
val IdziennikFeatures = listOf(
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_TIMETABLE, listOf(
ENDPOINT_IDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_GRADES, listOf(
ENDPOINT_IDZIENNIK_WEB_GRADES to LOGIN_METHOD_IDZIENNIK_WEB,
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_AGENDA, listOf(
ENDPOINT_IDZIENNIK_WEB_EXAMS 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)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ATTENDANCE, listOf(
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf(
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
/*Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),*/
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API))
)

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
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.getString
import pl.szczodrzynski.edziennik.utils.Utils
import java.net.HttpURLConnection
open class IdziennikApi(open val data: DataIdziennik) {
companion object {
const val TAG = "IdziennikApi"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun apiGet(tag: String, endpointTemplate: String, method: Int = GET, parameters: Map<String, Any> = emptyMap(), onSuccess: (json: JsonElement) -> Unit) {
val endpoint = endpointTemplate.replace("\$STUDENT_ID", data.studentId ?: "")
Utils.d(tag, "Request: Idziennik/API - $IDZIENNIK_API_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val json = try {
JsonParser().parse(text)
} catch (_: Exception) { null }
var error: String? = null
if (json == null) {
error = text
}
else if (json is JsonObject) {
error = if (response?.code() == 200) null else
json.getString("message") ?: json.toString()
}
error?.let { code ->
when (code) {
"Authorization has been denied for this request." -> ERROR_IDZIENNIK_API_ACCESS_DENIED
else -> ERROR_IDZIENNIK_API_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
try {
onSuccess(json!!)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_API_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))
}
}
Request.builder()
.url("$IDZIENNIK_API_URL/$endpoint")
.userAgent(IDZIENNIK_API_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiBearer}")
.apply {
when (method) {
GET -> get()
POST -> {
postJson()
val json = JsonObject()
parameters.map { (name, value) ->
when (value) {
is JsonObject -> json.add(name, value)
is JsonArray -> json.add(name, value)
is String -> json.addProperty(name, value)
is Int -> json.addProperty(name, value)
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
}
}
setJsonBody(json)
}
}
}
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.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.utils.Utils
class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikData"
}
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_IDZIENNIK_WEB_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
IdziennikWebTimetable(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
IdziennikWebGrades(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
IdziennikWebProposedGrades(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_EXAMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
IdziennikWebExams(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
IdziennikWebNotices(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_announcements)
IdziennikWebAnnouncements(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
IdziennikWebAttendance(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
IdziennikApiCurrentRegister(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
IdziennikApiMessagesInbox(data) { onSuccess() }
}
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
IdziennikApiMessagesSent(data) { onSuccess() }
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.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.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.utils.Utils.d
import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
open class IdziennikWeb(open val data: DataIdziennik) {
companion object {
const val TAG = "IdziennikWeb"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun webApiGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Idziennik/Web/API - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
when {
response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED
response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response?.parserErrorBody != null -> when {
response.parserErrorBody.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response.parserErrorBody.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
response.parserErrorBody.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}
else -> null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json?.toString() ?: response?.parserErrorBody)
.withResponse(response))
return
}
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_API_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("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.postJson()
.apply {
val json = JsonObject()
parameters.map { (name, value) ->
when (value) {
is JsonObject -> json.add(name, value)
is JsonArray -> json.add(name, value)
is String -> json.addProperty(name, value)
is Int -> json.addProperty(name, value)
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
}
}
setJsonBody(json)
}
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) {
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
if (!text.contains("czyWyswietlicDostepMobilny")) {
when {
text.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_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))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.api.v2.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.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 IdziennikApiCurrentRegister(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikApi(data) {
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()
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) }
}
json.getInt("szczesliwyNumerek")?.let { luckyNumber ->
val luckyNumberDate = Date.getToday()
settings.getString("godzinaPublikacjiSzczesliwegoLosu")
?.let { Time.fromH_m(it) }
?.let { publishTime ->
val now = Time.getNow()
if (publishTime.value < 150000 && now.value < publishTime.value) {
nextSync = luckyNumberDate.combineWith(publishTime)
luckyNumberDate.stepForward(0, 0, -1) // the lucky number is still for yesterday
}
else if (publishTime.value >= 150000 && now.value > publishTime.value) {
luckyNumberDate.stepForward(0, 0, 1) // the lucky number is already for tomorrow
nextSync = luckyNumberDate.combineWith(publishTime)
}
else if (publishTime.value < 150000) {
nextSync = luckyNumberDate
.clone()
.stepForward(0, 0, 1)
.combineWith(publishTime)
}
else {
nextSync = luckyNumberDate.combineWith(publishTime)
}
}
val luckyNumberObject = LuckyNumber(
data.profileId,
Date.getToday(),
luckyNumber
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
data.profile?.empty ?: false,
data.profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync)
onSuccess()
}
}
}

View File

@ -0,0 +1,94 @@
/*
* 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(), "<br>")
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.messageList.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()
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.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.utils.Utils.crc32
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikApiMessagesSent(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikApi(data) {
companion object {
private const val TAG = "IdziennikApiMessagesSent"
}
init {
apiGet(TAG, IDZIENNIK_API_MESSAGES_SENT) { json ->
if (json !is JsonArray) {
onSuccess()
return@apiGet
}
json.asJsonObjectList()?.forEach { jMessage ->
val messageIdStr = jMessage.get("id").asString
val messageId = crc32((messageIdStr + "1").toByteArray())
val subject = jMessage.get("tytul").asString
var body = "[META:$messageIdStr;-1]"
body += jMessage.get("tresc").asString.replace("\n".toRegex(), "<br>")
val sentDate = Date.fromIso(jMessage.get("dataWyslania").asString)
val message = Message(
profileId,
messageId,
subject,
body,
TYPE_SENT,
-1,
-1
)
for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) {
val recipient = recipientEl.asJsonObject
var firstName = recipient.get("imie").asString
var lastName = recipient.get("nazwisko").asString
if (firstName.isEmpty() || lastName.isEmpty()) {
firstName = "usunięty"
lastName = "użytkownik"
}
val rTeacher = data.getTeacher(firstName, lastName)
rTeacher.loginId = recipient.get("id").asString + ":" + recipient.get("usr").asString
val messageRecipient = MessageRecipient(
profileId,
rTeacher.id,
-1,
-1,
/*messageId*/ messageId
)
data.messageRecipientIgnoreList.add(messageRecipient)
}
data.messageList.add(message)
data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
onSuccess()
}
}
}

View File

@ -0,0 +1,74 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,144 @@
/*
* 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()
}
}
}
}

View File

@ -0,0 +1,114 @@
/*
* 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()
}
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.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.utils.models.Date
class IdziennikWebGrades(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebGrades"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_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
}
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.onEach { subjectJson ->
val subject = data.getSubject(
subjectJson.getString("Przedmiot") ?: return@onEach,
subjectJson.getLong("IdPrzedmiotu") ?: return@onEach,
subjectJson.getString("Przedmiot") ?: return@onEach
)
subjectJson.getJsonArray("Oceny")?.asJsonObjectList()?.forEach { grade ->
val id = grade.getLong("idK") ?: return@forEach
val category = grade.getString("Kategoria") ?: ""
val name = grade.getString("Ocena") ?: "?"
val semester = grade.getInt("Semestr") ?: 1
val teacher = data.getTeacherByLastFirst(grade.getString("Wystawil") ?: return@forEach)
val countToAverage = grade.getBoolean("DoSredniej") ?: true
var value = grade.getFloat("WartoscDoSred") ?: 0.0f
val weight = if (countToAverage)
grade.getFloat("Waga") ?: 0.0f
else
0.0f
val gradeColor = grade.getString("Kolor") ?: ""
var colorInt = 0xff2196f3.toInt()
if (gradeColor.isNotEmpty()) {
colorInt = Color.parseColor("#$gradeColor")
}
val gradeObject = Grade(
profileId,
id,
category,
colorInt,
"",
name,
value,
weight,
semester,
teacher.id,
subject.id)
when (grade.getInt("Typ")) {
0 -> {
val history = grade.getJsonArray("Historia")?.asJsonObjectList()
if (history?.isNotEmpty() == true) {
var sum = gradeObject.value * gradeObject.weight
var count = gradeObject.weight
for (historyItem in history) {
val countToTheAverage = historyItem.getBoolean("DoSredniej") ?: false
value = historyItem.get("WartoscDoSred").asFloat
val weight = historyItem.get("Waga").asFloat
if (value > 0 && countToTheAverage) {
sum += value * weight
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 addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
data.gradeList.add(historyObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
historyObject.id,
true,
true,
addedDate
))
}
// update the current grade's value with an average of all historical grades and itself
if (sum > 0 && count > 0) {
gradeObject.value = sum / count
}
gradeObject.isImprovement = true // gradeObject is the improved grade. Originals are historyObjects
}
}
1 -> {
gradeObject.type = Grade.TYPE_SEMESTER1_FINAL
gradeObject.name = name
gradeObject.weight = 0f
}
2 -> {
gradeObject.type = Grade.TYPE_YEAR_FINAL
gradeObject.name = name
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.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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_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.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebNotices(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebNotices"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_NOTICES, 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
}
for (jNoticeEl in json.getAsJsonArray("SUwaga")) {
val jNotice = jNoticeEl.asJsonObject
// jNotice
val noticeId = jNotice.get("id").asString.crc16().toLong()
val rTeacher = data.getTeacherByLastFirst(jNotice.get("Nauczyciel").asString)
val addedDate = Date.fromY_m_d(jNotice.get("Data").asString)
var nType = TYPE_NEUTRAL
val jType = jNotice.get("Typ").asString
if (jType == "n") {
nType = TYPE_NEGATIVE
} else if (jType == "p") {
nType = TYPE_POSITIVE
}
val noticeObject = Notice(
profileId,
noticeId,
jNotice.get("Tresc").asString,
jNotice.get("Semestr").asInt,
nType,
rTeacher.id)
data.noticeList.add(noticeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_NOTICE,
noticeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate.inMillis
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_NOTICES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,108 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,128 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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<Profile>()
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()
}
}
}
}

View File

@ -1,18 +1,18 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-6.
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
package pl.szczodrzynski.edziennik.api.v2.idziennik.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_HEBE
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
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.utils.Utils
class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "VulcanLogin"
private const val TAG = "IdziennikLogin"
}
private var cancelled = false
@ -46,13 +46,13 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_VULCAN_WEB_MAIN -> {
data.startProgress(R.string.edziennik_progress_login_vulcan_web_main)
VulcanLoginWebMain(data) { onSuccess(loginMethodId) }
LOGIN_METHOD_IDZIENNIK_WEB -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_web)
IdziennikLoginWeb(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_VULCAN_HEBE -> {
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
VulcanLoginHebe(data) { onSuccess(loginMethodId) }
LOGIN_METHOD_IDZIENNIK_API -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_api)
IdziennikLoginApi(data) { onSuccess(loginMethodId) }
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.login
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginApi"
}
init { run {
if (data.isApiLoginValid()) {
onSuccess()
}
else {
onSuccess()
}
}}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-26.
*/
package pl.szczodrzynski.edziennik.api.v2.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.utils.Utils
class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginWeb"
}
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()
))
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("iuczniowie.progman.pl")
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
loginWithCredentials()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun loginWithCredentials() {
Utils.d(TAG, "Request: Idziennik/Login/Web - $IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
val loginCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
// login succeeded: there is a start page
if (text.contains("czyWyswietlicDostepMobilny")) {
val cookies = data.app.cookieJar.getForDomain("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.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. */
return@run null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
onSuccess()
return
}
val errorText = Regexes.IDZIENNIK_LOGIN_ERROR.find(text)?.get(1)
when {
errorText?.contains("nieprawidłową nazwę szkoły") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME
errorText?.contains("nieprawidłowy login lub hasło") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN
text.contains("Identyfikator zgłoszenia") -> ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_LOGIN_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
val getCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.addHeader("Origin", "https://iuczniowie.progman.pl")
.addHeader("Referer", "$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.apply {
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text ?: return@apply).forEach {
addParameter(it[1], it[2])
}
}
.addParameter("ctl00\$ContentPlaceHolder\$nazwaPrzegladarki", IDZIENNIK_USER_AGENT)
.addParameter("ctl00\$ContentPlaceHolder\$NazwaSzkoly", data.webSchoolName)
.addParameter("ctl00\$ContentPlaceHolder\$UserName", data.webUsername)
.addParameter("ctl00\$ContentPlaceHolder\$Password", data.webPassword)
.addParameter("ctl00\$ContentPlaceHolder\$captcha", "")
.addParameter("ctl00\$ContentPlaceHolder\$Logowanie", "Zaloguj")
.post()
.allowErrorCode(502)
.callback(loginCallback)
.build()
.enqueue()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.allowErrorCode(502)
.callback(getCallback)
.build()
.enqueue()
}
}

View File

@ -2,10 +2,10 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.data.api.interfaces
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.data.api.models.Feature
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
import pl.szczodrzynski.edziennik.api.v2.models.Feature
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed only to an e-register class.
@ -14,4 +14,4 @@ import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
*/
interface EdziennikCallback : EndpointCallback {
fun onCompleted()
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
interface EdziennikInterface {
fun sync(featureIds: List<Int>, viewId: Int? = null)
fun getMessage(messageId: Int)
fun markAllAnnouncementsAsRead()
fun firstLogin()
fun cancel()
}

View File

@ -2,11 +2,11 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.data.api.interfaces
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.Feature
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Feature
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed to all [Feature]s and [LoginMethod]s
@ -15,4 +15,4 @@ interface EndpointCallback {
fun onError(apiError: ApiError)
fun onProgress(step: Float)
fun onStartProgress(stringRes: Int)
}
}

View File

@ -2,17 +2,18 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus
package pl.szczodrzynski.edziennik.api.v2.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.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.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
@ -30,16 +31,26 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
loginMethods += LOGIN_METHOD_LIBRUS_API
if (isSynergiaLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId)
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
}
if (isMessagesLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId)
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
}
}
override fun generateUserCode() = "$schoolName:$apiLogin"
fun getColor(id: Int?): Int {
return when (id) {
1 -> 0xFFF0E68C
@ -120,7 +131,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); mApiLogin = value }
set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value }
/**
* A Synergia password.
* Used: for login (API Login Method) in Synergia mode.
@ -129,7 +140,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); mApiPassword = value }
set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value }
/**
* A JST login Code.
@ -137,16 +148,16 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
*/
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value); mApiCode = value }
get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value) ?: return; 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 ?: loginStore.getLoginData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value); mApiPin = value }
get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value }
/**
* A Synergia API access token.
@ -157,7 +168,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) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; }
set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value }
/**
* A Synergia API refresh token.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
@ -165,7 +176,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) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; }
set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value }
/**
* The expiry time for [apiAccessToken], as a UNIX timestamp.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
@ -174,17 +185,7 @@ 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) { 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; }
set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value }
/* _____ _
/ ____| (_)
@ -255,7 +256,6 @@ 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,20 +265,8 @@ 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 }
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
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
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.librus.data.LibrusData
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.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librusLoginMethods
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.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Librus"
}
val internalErrorList = mutableListOf<Int>()
val data: DataLibrus
init {
data = DataLibrus(app, profile, loginStore).apply {
callback = wrapCallback(this@Librus.callback)
satisfyLoginMethods()
}
}
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>, viewId: Int?) {
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
LibrusLogin(data) {
LibrusData(data) {
completed()
}
}
}
override fun getMessage(messageId: Int) {
}
override fun markAllAnnouncementsAsRead() {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusSynergiaMarkAllAnnouncementsAsRead(data) {
completed()
}
}
}
}
override fun firstLogin() {
LibrusFirstLogin(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)
}
}
}
}
}

View File

@ -2,71 +2,64 @@
* Copyright (c) Kuba Szczodrzyński 2019-10-11.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus
package pl.szczodrzynski.edziennik.api.v2.librus
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.Feature
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.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_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
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
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 as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId)
data.app.appConfig.fcmTokens[LOGIN_TYPE_LIBRUS]?.second?.contains(data.profileId) == false
},
@ -98,15 +91,12 @@ val LibrusFeatures = listOf(
* All grades + categories.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
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_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_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,
@ -118,11 +108,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.
*/
@ -229,9 +219,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.

View File

@ -2,20 +2,20 @@
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data
package pl.szczodrzynski.edziennik.api.v2.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.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.models.ApiError
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.getString
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
open class LibrusApi(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusApi"
}
@ -26,30 +26,20 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
val profile
get() = data.profile
fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, ignoreErrors: List<Int> = emptyList(), onSuccess: (json: JsonObject) -> Unit) {
fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
d(tag, "Request: Librus/Api - $LIBRUS_API_URL/$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.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) {
@ -66,17 +56,12 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
"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 ->
if (errorCode !in ignoreErrors) {
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
@ -105,7 +90,7 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
}
Request.builder()
.url("${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
.url("$LIBRUS_API_URL/$endpoint")
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiAccessToken}")
.apply {
@ -119,11 +104,8 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.allowErrorCode(HTTP_NOT_FOUND)
.allowErrorCode(503)
.callback(callback)
.build()
.enqueue()
}
}
}

View File

@ -0,0 +1,163 @@
/*
* 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
// TODO timetable
ENDPOINT_LIBRUS_API_NORMAL_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(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() }
}
ENDPOINT_LIBRUS_MESSAGES_SENT -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
LibrusMessagesGetList(data, type = Message.TYPE_SENT) { onSuccess() }
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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 org.redundent.kotlin.xml.PrintOptions
import org.redundent.kotlin.xml.xml
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.get
import pl.szczodrzynski.edziennik.utils.Utils.d
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<String, Any>? = 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(LibrusSynergia.TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
// TODO: Finish error handling
if ("error" in text) {
when ("<type>(.*)</type>".toRegex().find(text)?.get(1)) {
"eAccessDeny" -> data.error(ApiError(tag, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED)
.withResponse(response)
.withApiResponse(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 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()
}
}

View File

@ -1,12 +1,12 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data
package pl.szczodrzynski.edziennik.api.v2.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.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.models.ApiError
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.getString
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection
@ -24,7 +24,7 @@ open class LibrusPortal(open val data: DataLibrus) {
fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) {
d(tag, "Request: Librus/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL}$endpoint")
d(tag, "Request: Librus/Portal - $LIBRUS_PORTAL_URL$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
@ -44,11 +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
}
else -> ERROR_LIBRUS_PORTAL_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
@ -82,7 +78,7 @@ open class LibrusPortal(open val data: DataLibrus) {
}
Request.builder()
.url((if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL) + endpoint)
.url(LIBRUS_PORTAL_URL + endpoint)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.portalAccessToken}")
.apply {
@ -98,9 +94,8 @@ 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()
}
}
}

View File

@ -2,19 +2,19 @@
* Copyright (c) Kacper Ziubryniewicz 2019-10-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data
package pl.szczodrzynski.edziennik.api.v2.librus.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.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.models.ApiError
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
open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) {
open class LibrusSynergia(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusSynergia"
const val TAG = "LibrusSynergia"
}
val profileId
@ -35,7 +35,7 @@ open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) {
return
}
if (!text.contains("jesteś zalogowany") && !text.contains("Podgląd zadania")) {
if (!text.contains("jesteś zalogowany")) {
when {
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
text.contains("Przerwa techniczna") -> ERROR_LIBRUS_SYNERGIA_MAINTENANCE
@ -48,6 +48,7 @@ open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) {
}
}
try {
onSuccess(text)
} catch (e: Exception) {
@ -89,44 +90,4 @@ open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) {
.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()
}
}

View File

@ -0,0 +1,63 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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()
}
}
}

View File

@ -2,22 +2,20 @@
* Copyright (c) Kacper Ziubryniewicz 2019-10-14
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.DAY
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.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.getJsonObject
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiClasses(override val data: DataLibrus,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : LibrusApi(data, lastSync) {
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiClasses"
}
@ -40,8 +38,6 @@ class LibrusApiClasses(override val data: DataLibrus,
teacherId
)
data.profile?.studentClassName = name
data.teamList.put(id, teamObject)
data.unitId = studentClass.getJsonObject("Unit").getLong("Id") ?: 0L
@ -57,7 +53,7 @@ class LibrusApiClasses(override val data: DataLibrus,
}
data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSES, 4 * DAY)
onSuccess(ENDPOINT_LIBRUS_API_CLASSES)
onSuccess()
}
}
}

View File

@ -2,35 +2,32 @@
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
package pl.szczodrzynski.edziennik.api.v2.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_CLASSROOMS
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.entity.Classroom
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 java.util.*
class LibrusApiClassrooms(override val data: DataLibrus,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : LibrusApi(data, lastSync) {
val onSuccess: () -> Unit) : LibrusApi(data) {
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.fixWhiteSpaces().split(" ").onEach { it[0] }.joinToString()
val symbolParts = symbol.fixWhiteSpaces().split(" ")
val nameShort = name.split(" ").onEach { it[0] }.joinToString()
val friendlyName = if (name != symbol && !name.contains(symbol) && !name.containsAll(symbolParts) && !nameShort.contains(symbol)) {
val friendlyName = if (name != symbol && !name.contains(symbol) && !nameShort.contains(symbol)) {
classroom.getString("Symbol") + " " + classroom.getString("Name")
}
else {
@ -41,7 +38,7 @@ class LibrusApiClassrooms(override val data: DataLibrus,
}
data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSROOMS, 4*DAY)
onSuccess(ENDPOINT_LIBRUS_API_CLASSROOMS)
onSuccess()
}
}
}

View File

@ -2,25 +2,23 @@
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
package pl.szczodrzynski.edziennik.api.v2.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_EVENT_TYPES
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.entity.EventType
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
class LibrusApiEventTypes(override val data: DataLibrus,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : LibrusApi(data, lastSync) {
val onSuccess: () -> Unit) : LibrusApi(data) {
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
@ -31,7 +29,7 @@ class LibrusApiEventTypes(override val data: DataLibrus,
}
data.setSyncNext(ENDPOINT_LIBRUS_API_EVENT_TYPES, 4*DAY)
onSuccess(ENDPOINT_LIBRUS_API_EVENT_TYPES)
onSuccess()
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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()
}
}
}

View File

@ -0,0 +1,97 @@
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()
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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()
}
}
}

View File

@ -2,26 +2,30 @@
* Copyright (c) Kacper Ziubryniewicz 2019-10-14
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
package pl.szczodrzynski.edziennik.api.v2.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_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.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.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiLuckyNumber(override val data: DataLibrus,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : LibrusApi(data, lastSync) {
val onSuccess: () -> Unit) : LibrusApi(data) {
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 ->
@ -33,15 +37,14 @@ 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 = profileId,
date = luckyNumberDate,
number = luckyNumber
profileId,
luckyNumberDate,
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(
@ -49,14 +52,15 @@ class LibrusApiLuckyNumber(override val data: DataLibrus,
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
true,
profile?.empty ?: false
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_LUCKY_NUMBER, syncAt = nextSync)
onSuccess(ENDPOINT_LIBRUS_API_LUCKY_NUMBER)
onSuccess()
}
}
}

Some files were not shown because too many files have changed in this diff Show More