mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-13 14:10:46 +02:00
Compare commits
96 Commits
v4.2.1
...
v4.6-beta.
Author | SHA1 | Date | |
---|---|---|---|
359432d24d | |||
edf8ec20f0 | |||
3ad9e5da1f | |||
459bbf78b2 | |||
d0baf02750 | |||
a5bb7d9c6e | |||
a939d95ea3 | |||
4c081c970e | |||
b75ab76c2a | |||
9da6dbccb3 | |||
66444ae35b | |||
3e8b3de2b7 | |||
85ac5769a1 | |||
93e3d5994a | |||
0cf24c527b | |||
97c5acd6ba | |||
30aeb70647 | |||
b599d679c4 | |||
1eecd24d91 | |||
c698dfdb73 | |||
c7a44f5ced | |||
c8ee6ff1e7 | |||
552acd4043 | |||
ede101ea20 | |||
13c2640ed5 | |||
dd0739fd4b | |||
9023f13932 | |||
f8456fb087 | |||
9b48041cd9 | |||
46cecf3474 | |||
c27254bcad | |||
80333cdea4 | |||
6aee3ea420 | |||
a11a44b768 | |||
e869107101 | |||
5903bbe59d | |||
6c0ddd3e6d | |||
621a7ac642 | |||
e86b47fb1b | |||
98fb7ac8c9 | |||
f49e39e858 | |||
8fc57cd3f5 | |||
a9eda087e0 | |||
3f36a284ee | |||
1814fd67e1 | |||
54e49af943 | |||
d6a67a0da6 | |||
28725c6400 | |||
4fc965d970 | |||
2aaf713d58 | |||
c7d2ac4e3e | |||
ae20c30c88 | |||
aef3f66654 | |||
c7abde8f11 | |||
2fcff33bd6 | |||
b08e4c2d3d | |||
73ff09052c | |||
9649afd43f | |||
ed3a245b51 | |||
477730708f | |||
f39d0c595d | |||
46407f9647 | |||
6ecb97b87e | |||
ecdaaeae65 | |||
a0c302b663 | |||
b31039ecd9 | |||
5c84086f42 | |||
752cdfa8d6 | |||
8e3d404352 | |||
810cfd8092 | |||
bd2a9524c6 | |||
d780d5118d | |||
f1570b8eb9 | |||
de0f29a09e | |||
c0d11c91e3 | |||
22c540a3d4 | |||
b7e35d0322 | |||
7bcd6bf038 | |||
ea4591144b | |||
7627d184a2 | |||
076b485fda | |||
09cb97e367 | |||
4e1f2ed41a | |||
281b6a95ef | |||
e40871c0d0 | |||
b74eeed994 | |||
ccde482364 | |||
a02033d0f3 | |||
6c6bc89f57 | |||
1e3da45340 | |||
0d366adddb | |||
2c24eba46d | |||
7c6dbca986 | |||
33a8fa2a1e | |||
300e2c4bc2 | |||
f883318bd2 |
16
.idea/codeStyles/Project.xml
generated
16
.idea/codeStyles/Project.xml
generated
@ -1,6 +1,10 @@
|
||||
<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>
|
||||
@ -11,7 +15,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -22,7 +25,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -34,7 +36,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -45,7 +46,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -56,7 +56,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -67,7 +66,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -78,7 +76,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -90,7 +87,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -102,7 +98,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@ -112,5 +107,8 @@
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
17
.idea/compiler.xml
generated
Normal file
17
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?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
2
.idea/discord.xml
generated
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="true" />
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
<component name="ProjectNotificationSettings">
|
||||
<option name="askShowProject" value="false" />
|
||||
|
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -11,7 +11,6 @@
|
||||
<item index="1" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
||||
<option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" />
|
||||
@ -51,7 +50,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
1
.idea/runConfigurations.xml
generated
1
.idea/runConfigurations.xml
generated
@ -3,6 +3,7 @@
|
||||
<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" />
|
||||
|
@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
@ -54,10 +54,11 @@ android {
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
dataBinding {
|
||||
enabled = true
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility '1.8'
|
||||
targetCompatibility '1.8'
|
||||
}
|
||||
@ -75,6 +76,7 @@ android {
|
||||
version "3.10.2"
|
||||
}
|
||||
}
|
||||
ndkVersion '21.3.6528147'
|
||||
}
|
||||
|
||||
/*task finalizeBundleDebug(type: Copy) {
|
||||
@ -104,6 +106,8 @@ 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"
|
||||
|
||||
@ -114,7 +118,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:${versions.lifecycle}"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycle}"
|
||||
implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}"
|
||||
implementation "androidx.room:room-runtime:${versions.room}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
|
||||
@ -137,7 +141,7 @@ dependencies {
|
||||
|
||||
implementation "cat.ereza:customactivityoncrash:2.2.0"
|
||||
implementation "com.applandeo:material-calendar-view:1.5.0"
|
||||
implementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.3.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"
|
||||
@ -147,7 +151,7 @@ dependencies {
|
||||
implementation "com.jaredrummler:colorpicker:1.0.2"
|
||||
implementation("com.squareup.okhttp3:okhttp") {
|
||||
version {
|
||||
strictly "3.12.2"
|
||||
strictly "3.12.13"
|
||||
}
|
||||
}
|
||||
implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update
|
||||
@ -157,7 +161,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.10.1"
|
||||
implementation "org.jsoup:jsoup:1.12.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'
|
||||
@ -180,6 +184,7 @@ dependencies {
|
||||
//implementation "org.redundent:kotlin-xml-builder:1.5.3"
|
||||
|
||||
implementation "io.github.wulkanowy:signer-android:0.1.1"
|
||||
implementation 'com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:${versions.work}"
|
||||
|
||||
@ -203,10 +208,10 @@ dependencies {
|
||||
|
||||
implementation 'com.google.android:flexbox:2.0.1'
|
||||
|
||||
implementation 'com.qifan.powerpermission:powerpermission:1.0.0'
|
||||
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0'
|
||||
implementation 'com.qifan.powerpermission:powerpermission:1.3.0'
|
||||
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0'
|
||||
|
||||
implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT'
|
||||
implementation '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"
|
||||
|
@ -64,6 +64,9 @@
|
||||
|
||||
-keep class pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing { public final byte[] pleaseStopRightNow(java.lang.String, long); }
|
||||
|
||||
-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
|
||||
-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
|
||||
-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo.Platform { *; }
|
||||
-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 { *; }
|
||||
|
@ -1,11 +1,10 @@
|
||||
<h3>Wersja 4.2.1, 2020-05-21</h3>
|
||||
<h3>Wersja 4.6-beta.1, 2021-02-25</h3>
|
||||
<ul>
|
||||
<li>Poprawiona błędna godzina synchronizacji danych (przesunięcie w przód o 1-2h).</li>
|
||||
<li>Vulcan: Naprawione logowanie.</li>
|
||||
<li>Mobidziennik: Poprawione pobieranie szczęśliwego numerka.</li>
|
||||
<li>Prymus: Naprawione logowane.</li>
|
||||
<li>Vulcan: dodano możliwość logowania adresem e-mail lub nazwą użytkownika.</li>
|
||||
<li>Vulcan: dodano obsługę załączników w wiadomościach i zadaniach domowych.</li>
|
||||
<li>Zalecane jest zalogowanie się ponownie.</li>
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
Dzięki za korzystanie ze Szkolnego!<br>
|
||||
<i>© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i>
|
||||
<i>© Kuba Szczodrzyński, Kacper Ziubryniewicz 2021</i>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/*secret password - removed for source code publication*/
|
||||
static toys AES_IV[16] = {
|
||||
0x43, 0xfc, 0x2d, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
0xe5, 0x07, 0x0f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||
|
||||
|
@ -57,8 +57,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
val profileId
|
||||
get() = profile.id
|
||||
|
||||
var devMode = false
|
||||
var debugMode = false
|
||||
var devMode = false
|
||||
}
|
||||
|
||||
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
|
||||
@ -107,7 +107,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
builder.installHttpsSupport(this)
|
||||
|
||||
if (debugMode || BuildConfig.DEBUG) {
|
||||
if (devMode || BuildConfig.DEBUG) {
|
||||
HyperLog.initialize(this)
|
||||
HyperLog.setLogLevel(Log.VERBOSE)
|
||||
HyperLog.setLogFormat(DebugLogFormat(this))
|
||||
@ -162,7 +162,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
Iconics.registerFont(SzkolnyFont)
|
||||
App.db = AppDb(this)
|
||||
Themes.themeInt = config.ui.theme
|
||||
debugMode = config.debugMode
|
||||
devMode = config.debugMode
|
||||
MHttp.instance().customOkHttpClient(http)
|
||||
|
||||
if (!profileLoadById(config.lastProfileId)) {
|
||||
@ -173,9 +173,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
setLanguage(it)
|
||||
}
|
||||
|
||||
devMode = BuildConfig.DEBUG
|
||||
debugMode = BuildConfig.DEBUG
|
||||
if (BuildConfig.DEBUG)
|
||||
debugMode = true
|
||||
devMode = true
|
||||
|
||||
Signing.getCert(this)
|
||||
|
||||
@ -185,7 +185,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
|
||||
if (config.devModePassword != null)
|
||||
checkDevModePassword()
|
||||
debugMode = devMode || config.debugMode
|
||||
devMode = debugMode || config.debugMode
|
||||
|
||||
if (config.sync.enabled)
|
||||
SyncWorker.scheduleNext(this@App, false)
|
||||
@ -294,6 +294,19 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
"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
|
||||
@ -324,6 +337,14 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
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()
|
||||
@ -364,6 +385,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
if (!success) {
|
||||
EventBus.getDefault().post(ProfileListEmptyEvent())
|
||||
}
|
||||
else {
|
||||
onSuccess(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun profileSave() = profileSave(profile)
|
||||
|
@ -42,10 +42,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.gson.*
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import im.wangchao.mhttp.Response
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
@ -95,30 +95,30 @@ fun List<Teacher>.byNameFDotSpaceLast(nameFDotSpaceLast: String) = firstOrNull {
|
||||
|
||||
fun JsonObject?.get(key: String): JsonElement? = this?.get(key)
|
||||
|
||||
fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (it.isJsonNull) null else it.asBoolean }
|
||||
fun JsonObject?.getString(key: String): String? = get(key)?.let { if (it.isJsonNull) null else it.asString }
|
||||
fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) null else it.asInt }
|
||||
fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (it.isJsonNull) null else it.asLong }
|
||||
fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(it.isJsonNull) null else it.asFloat }
|
||||
fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(it.isJsonNull) null else it.asCharacter }
|
||||
fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asBoolean }
|
||||
fun JsonObject?.getString(key: String): String? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asString }
|
||||
fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asInt }
|
||||
fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asLong }
|
||||
fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(!it.isJsonPrimitive) null else it.asFloat }
|
||||
fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(!it.isJsonPrimitive) null else it.asCharacter }
|
||||
fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonObject) it.asJsonObject else null }
|
||||
fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonArray) it.asJsonArray else null }
|
||||
|
||||
fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (it.isJsonNull) defaultValue else it.asBoolean } ?: defaultValue
|
||||
fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (it.isJsonNull) defaultValue else it.asString } ?: defaultValue
|
||||
fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (it.isJsonNull) defaultValue else it.asInt } ?: defaultValue
|
||||
fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (it.isJsonNull) defaultValue else it.asLong } ?: defaultValue
|
||||
fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(it.isJsonNull) defaultValue else it.asFloat } ?: defaultValue
|
||||
fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(it.isJsonNull) defaultValue else it.asCharacter } ?: defaultValue
|
||||
fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asBoolean } ?: defaultValue
|
||||
fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asString } ?: defaultValue
|
||||
fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asInt } ?: defaultValue
|
||||
fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asLong } ?: defaultValue
|
||||
fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(!it.isJsonPrimitive) defaultValue else it.asFloat } ?: defaultValue
|
||||
fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(!it.isJsonPrimitive) defaultValue else it.asCharacter } ?: defaultValue
|
||||
fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonObject) it.asJsonObject else defaultValue } ?: defaultValue
|
||||
fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonArray) it.asJsonArray else defaultValue } ?: defaultValue
|
||||
|
||||
fun JsonArray.getBoolean(key: Int): Boolean? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asBoolean }
|
||||
fun JsonArray.getString(key: Int): String? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asString }
|
||||
fun JsonArray.getInt(key: Int): Int? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asInt }
|
||||
fun JsonArray.getLong(key: Int): Long? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asLong }
|
||||
fun JsonArray.getFloat(key: Int): Float? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asFloat }
|
||||
fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asCharacter }
|
||||
fun JsonArray.getBoolean(key: Int): Boolean? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asBoolean }
|
||||
fun JsonArray.getString(key: Int): String? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asString }
|
||||
fun JsonArray.getInt(key: Int): Int? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asInt }
|
||||
fun JsonArray.getLong(key: Int): Long? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asLong }
|
||||
fun JsonArray.getFloat(key: Int): Float? = if (key >= size()) null else get(key)?.let { if(!it.isJsonPrimitive) null else it.asFloat }
|
||||
fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?.let { if(!it.isJsonPrimitive) null else it.asCharacter }
|
||||
fun JsonArray.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null }
|
||||
fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null }
|
||||
|
||||
@ -298,7 +298,7 @@ fun colorFromCssName(name: String): Int {
|
||||
"orange" -> 0xffffa500
|
||||
"black" -> 0xff000000
|
||||
"white" -> 0xffffffff
|
||||
else -> -1
|
||||
else -> -1L
|
||||
}.toInt()
|
||||
}
|
||||
|
||||
@ -537,6 +537,12 @@ fun String.md5(): String {
|
||||
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
fun String.sha1Hex(): String {
|
||||
val md = MessageDigest.getInstance("SHA-1")
|
||||
md.update(toByteArray())
|
||||
return md.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
fun String.sha256(): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
md.update(toByteArray())
|
||||
@ -672,6 +678,16 @@ fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
|
||||
text = context.getString(resid, *formatArgs)
|
||||
}
|
||||
|
||||
fun MaterialAlertDialogBuilder.setTitle(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder {
|
||||
setTitle(context.getString(resid, *formatArgs))
|
||||
return this
|
||||
}
|
||||
|
||||
fun MaterialAlertDialogBuilder.setMessage(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder {
|
||||
setMessage(context.getString(resid, *formatArgs))
|
||||
return this
|
||||
}
|
||||
|
||||
fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
||||
return JsonObject().apply {
|
||||
for (property in properties) {
|
||||
@ -686,6 +702,21 @@ fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonObject.toBundle(): Bundle {
|
||||
return Bundle().also {
|
||||
for ((key, value) in this.entrySet()) {
|
||||
when (value) {
|
||||
is JsonObject -> it.putBundle(key, value.toBundle())
|
||||
is JsonPrimitive -> when {
|
||||
value.isString -> it.putString(key, value.asString)
|
||||
value.isBoolean -> it.putBoolean(key, value.asBoolean)
|
||||
value.isNumber -> it.putInt(key, value.asInt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonArray(vararg properties: Any?): JsonArray {
|
||||
return JsonArray().apply {
|
||||
for (property in properties) {
|
||||
@ -1161,7 +1192,7 @@ fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null
|
||||
fun String.copyToClipboard(context: Context) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText("Tekst", this)
|
||||
clipboard.primaryClip = clipData
|
||||
clipboard.setPrimaryClip(clipData)
|
||||
}
|
||||
|
||||
fun TextView.getTextPosition(range: IntRange): Rect {
|
||||
|
@ -38,16 +38,22 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.*
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
|
||||
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
|
||||
import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.UpdateAvailableDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||
@ -59,6 +65,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.MainSnackbar
|
||||
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.debug.DebugFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.debug.LabFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
|
||||
@ -227,7 +234,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class).withPopTo(DRAWER_ITEM_MESSAGES)
|
||||
list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class)
|
||||
list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class)
|
||||
if (App.debugMode) {
|
||||
if (App.devMode) {
|
||||
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
|
||||
list += NavTarget(TARGET_LAB, R.string.menu_lab, LabFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_flask_outline)
|
||||
@ -295,12 +302,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
|
||||
if (BuildConfig.VERSION_NAME.contains("nightly")) {
|
||||
b.nightlyText.isVisible = true
|
||||
b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".")
|
||||
when {
|
||||
BuildConfig.VERSION_NAME.contains("nightly") -> {
|
||||
b.nightlyText.isVisible = true
|
||||
b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".")
|
||||
}
|
||||
BuildConfig.VERSION_NAME.contains("daily") -> {
|
||||
b.nightlyText.isVisible = true
|
||||
b.nightlyText.text = "Daily\n"+BuildConfig.VERSION_NAME.substringAfterLast(".")
|
||||
}
|
||||
BuildConfig.DEBUG -> {
|
||||
b.nightlyText.isVisible = true
|
||||
b.nightlyText.text = "Debug\n"+BuildConfig.VERSION_NAME
|
||||
}
|
||||
else -> b.nightlyText.isVisible = false
|
||||
}
|
||||
else
|
||||
b.nightlyText.isVisible = false
|
||||
|
||||
navLoading = true
|
||||
|
||||
@ -399,7 +415,20 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
}
|
||||
|
||||
app.db.profileDao().all.observe(this, Observer { profiles ->
|
||||
drawer.setProfileList(profiles.filter { it.id >= 0 }.toMutableList())
|
||||
val allArchived = profiles.all { it.archived }
|
||||
drawer.setProfileList(profiles.filter { it.id >= 0 && (!it.archived || allArchived) }.toMutableList())
|
||||
//prepend the archived profile if loaded
|
||||
if (app.profile.archived && !allArchived) {
|
||||
drawer.prependProfile(Profile(
|
||||
id = app.profile.id,
|
||||
loginStoreId = app.profile.loginStoreId,
|
||||
loginStoreType = app.profile.loginStoreType,
|
||||
name = app.profile.name,
|
||||
subname = "Archiwum - ${app.profile.subname}"
|
||||
).also {
|
||||
it.archived = true
|
||||
})
|
||||
}
|
||||
drawer.currentProfile = App.profileId
|
||||
})
|
||||
|
||||
@ -415,7 +444,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
})
|
||||
|
||||
b.swipeRefreshLayout.isEnabled = true
|
||||
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
|
||||
b.swipeRefreshLayout.setOnRefreshListener { launch { syncCurrentFeature() } }
|
||||
b.swipeRefreshLayout.setColorSchemeResources(
|
||||
R.color.md_blue_500,
|
||||
R.color.md_amber_500,
|
||||
@ -425,6 +454,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
SyncWorker.scheduleNext(app)
|
||||
UpdateWorker.scheduleNext(app)
|
||||
|
||||
// if loaded profile is archived, switch to the up-to-date version of it
|
||||
if (app.profile.archived) {
|
||||
launch {
|
||||
if (app.profile.archiveId != null) {
|
||||
val profile = withContext(Dispatchers.IO) {
|
||||
app.db.profileDao().getNotArchivedOf(app.profile.archiveId!!)
|
||||
}
|
||||
if (profile != null)
|
||||
loadProfile(profile)
|
||||
else
|
||||
loadProfile(0)
|
||||
} else {
|
||||
loadProfile(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// APP BACKGROUND
|
||||
if (app.config.ui.appBackground != null) {
|
||||
try {
|
||||
@ -522,7 +568,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline)
|
||||
.withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) })
|
||||
)
|
||||
if (App.debugMode) {
|
||||
if (App.devMode) {
|
||||
bottomSheet += BottomSheetPrimaryItem(false)
|
||||
.withTitle(R.string.menu_debug)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_android_studio)
|
||||
@ -564,7 +610,66 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|_____/ \__, |_| |_|\___|
|
||||
__/ |
|
||||
|__*/
|
||||
fun syncCurrentFeature() {
|
||||
suspend fun syncCurrentFeature() {
|
||||
if (app.profile.archived) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.profile_archived_title)
|
||||
.setMessage(
|
||||
R.string.profile_archived_text,
|
||||
app.profile.studentSchoolYearStart,
|
||||
app.profile.studentSchoolYearStart + 1
|
||||
)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
return
|
||||
}
|
||||
if (app.profile.shouldArchive()) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.profile_archiving_title)
|
||||
.setMessage(
|
||||
R.string.profile_archiving_format,
|
||||
app.profile.dateYearEnd.formattedString
|
||||
)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
if (app.profile.isBeforeYear()) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.profile_year_not_started_title)
|
||||
.setMessage(
|
||||
R.string.profile_year_not_started_format,
|
||||
app.profile.dateSemester1Start.formattedString
|
||||
)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
return
|
||||
}
|
||||
|
||||
app.profile.registerName?.let { registerName ->
|
||||
var status = app.config.sync.registerAvailability[registerName]
|
||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val api = SzkolnyApi(app)
|
||||
api.runCatching(this@MainActivity) {
|
||||
val availability = getRegisterAvailability()
|
||||
app.config.sync.registerAvailability = availability
|
||||
status = availability[registerName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status?.available != true
|
||||
|| status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
loadTarget(DRAWER_ITEM_HOME)
|
||||
if (status != null)
|
||||
RegisterUnavailableDialog(this, status!!)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show()
|
||||
val fragmentParam = when (navTargetId) {
|
||||
@ -581,6 +686,20 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
arguments = arguments
|
||||
).enqueue(this)
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onUpdateEvent(event: Update) {
|
||||
EventBus.getDefault().removeStickyEvent(event)
|
||||
UpdateAvailableDialog(this, event)
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) {
|
||||
EventBus.getDefault().removeStickyEvent(event)
|
||||
app.profile.registerName?.let { registerName ->
|
||||
event.data[registerName]?.let {
|
||||
RegisterUnavailableDialog(this, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onApiTaskStartedEvent(event: ApiTaskStartedEvent) {
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
@ -632,6 +751,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
|
||||
EventBus.getDefault().removeStickyEvent(event)
|
||||
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
|
||||
if (event.error.profileId != App.profileId)
|
||||
return
|
||||
ErrorDetailsDialog(this, listOf(event.error))
|
||||
}
|
||||
navView.toolbar.apply {
|
||||
subtitleFormat = R.string.toolbar_subtitle
|
||||
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
|
||||
@ -885,23 +1009,51 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
fun loadProfile(id: Int) = loadProfile(id, navTargetId)
|
||||
fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments)
|
||||
fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
|
||||
fun loadProfile(profile: Profile) = loadProfile(
|
||||
profile,
|
||||
navTargetId,
|
||||
null,
|
||||
if (app.profile.archived) app.profile.id else null
|
||||
)
|
||||
private fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
|
||||
if (App.profileId == id) {
|
||||
drawer.currentProfile = app.profile.id
|
||||
loadTarget(drawerSelection, arguments)
|
||||
return
|
||||
}
|
||||
val previousArchivedId = if (app.profile.archived) app.profile.id else null
|
||||
app.profileLoad(id) {
|
||||
MessagesFragment.pageSelection = -1
|
||||
|
||||
setDrawerItems()
|
||||
// the drawer profile is updated automatically when the drawer item is clicked
|
||||
// update it manually when switching profiles from other source
|
||||
//if (drawer.currentProfile != app.profile.id)
|
||||
drawer.currentProfile = app.profileId
|
||||
loadTarget(drawerSelection, arguments)
|
||||
loadProfile(it, drawerSelection, arguments, previousArchivedId)
|
||||
}
|
||||
}
|
||||
private fun loadProfile(profile: Profile, drawerSelection: Int, arguments: Bundle?, previousArchivedId: Int?) {
|
||||
App.profile = profile
|
||||
MessagesFragment.pageSelection = -1
|
||||
|
||||
setDrawerItems()
|
||||
|
||||
if (previousArchivedId != null) {
|
||||
// prevents accidentally removing the first item if the archived profile is not shown
|
||||
drawer.removeProfileById(previousArchivedId)
|
||||
}
|
||||
if (profile.archived) {
|
||||
drawer.prependProfile(Profile(
|
||||
id = profile.id,
|
||||
loginStoreId = profile.loginStoreId,
|
||||
loginStoreType = profile.loginStoreType,
|
||||
name = profile.name,
|
||||
subname = "Archiwum - ${profile.subname}"
|
||||
).also {
|
||||
it.archived = true
|
||||
})
|
||||
}
|
||||
|
||||
// the drawer profile is updated automatically when the drawer item is clicked
|
||||
// update it manually when switching profiles from other source
|
||||
//if (drawer.currentProfile != app.profile.id)
|
||||
drawer.currentProfile = app.profileId
|
||||
loadTarget(drawerSelection, arguments)
|
||||
}
|
||||
fun loadTarget(id: Int, arguments: Bundle? = null) {
|
||||
var loadId = id
|
||||
if (loadId == -1) {
|
||||
|
@ -105,6 +105,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
||||
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
||||
|
||||
private var mArchiverEnabled: Boolean? = null
|
||||
var archiverEnabled: Boolean
|
||||
get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true }
|
||||
set(value) { set("archiverEnabled", value); mArchiverEnabled = value }
|
||||
|
||||
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||
init {
|
||||
|
@ -4,12 +4,18 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.config
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.getIntList
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.config.utils.setMap
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class ConfigSync(private val config: Config) {
|
||||
private val gson = Gson()
|
||||
|
||||
private var mDontShowAppManagerDialog: Boolean? = null
|
||||
var dontShowAppManagerDialog: Boolean
|
||||
get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false }
|
||||
@ -93,6 +99,10 @@ class ConfigSync(private val config: Config) {
|
||||
var tokenVulcan: String?
|
||||
get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan }
|
||||
set(value) { config.set("tokenVulcan", value); mTokenVulcan = value }
|
||||
private var mTokenVulcanHebe: String? = null
|
||||
var tokenVulcanHebe: String?
|
||||
get() { mTokenVulcanHebe = mTokenVulcanHebe ?: config.values.get("tokenVulcanHebe", null as String?); return mTokenVulcanHebe }
|
||||
set(value) { config.set("tokenVulcanHebe", value); mTokenVulcanHebe = value }
|
||||
|
||||
private var mTokenMobidziennikList: List<Int>? = null
|
||||
var tokenMobidziennikList: List<Int>
|
||||
@ -106,4 +116,13 @@ class ConfigSync(private val config: Config) {
|
||||
var tokenVulcanList: List<Int>
|
||||
get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
|
||||
set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
|
||||
private var mTokenVulcanHebeList: List<Int>? = null
|
||||
var tokenVulcanHebeList: List<Int>
|
||||
get() { mTokenVulcanHebeList = mTokenVulcanHebeList ?: config.values.getIntList("tokenVulcanHebeList", listOf()); return mTokenVulcanHebeList ?: listOf() }
|
||||
set(value) { config.set("tokenVulcanHebeList", value); mTokenVulcanHebeList = value }
|
||||
|
||||
private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null
|
||||
var registerAvailability: Map<String, RegisterAvailabilityStatus>
|
||||
get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson<Map<String, RegisterAvailabilityStatus>>(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type) }; return mRegisterAvailability ?: mapOf() }
|
||||
set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value }
|
||||
}
|
||||
|
@ -49,4 +49,9 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
|
||||
var dontCountGrades: List<String>
|
||||
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
|
||||
set(value) { config.set("dontCountGrades", value); mDontCountGrades = value }
|
||||
|
||||
private var mHideSticksFromOld: Boolean? = null
|
||||
var hideSticksFromOld: Boolean
|
||||
get() { mHideSticksFromOld = mHideSticksFromOld ?: config.values.get("hideSticksFromOld", false); return mHideSticksFromOld ?: false }
|
||||
set(value) { config.set("hideSticksFromOld", value); mHideSticksFromOld = value }
|
||||
}
|
||||
|
@ -49,6 +49,9 @@ fun AbstractConfig.setIntList(key: String, value: List<Int>?) {
|
||||
fun AbstractConfig.setLongList(key: String, value: List<Long>?) {
|
||||
set(key, value?.let { gson.toJson(it) })
|
||||
}
|
||||
fun <K, V> AbstractConfig.setMap(key: String, value: Map<K, V>?) {
|
||||
set(key, value?.let { gson.toJson(it) })
|
||||
}
|
||||
|
||||
fun HashMap<String, String?>.get(key: String, default: String?): String? {
|
||||
return this[key] ?: default
|
||||
|
@ -24,14 +24,14 @@ const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
|
||||
|
||||
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||
const val LIBRUS_CLIENT_ID = "6XPsKf10LPz1nxgHQLcvZ1KM48DYzlBAhxipaXY8"
|
||||
const val LIBRUS_REDIRECT_URL = "http://localhost/bar"
|
||||
const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc"
|
||||
const val LIBRUS_REDIRECT_URL = "app://librus"
|
||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
||||
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
|
||||
|
||||
const val LIBRUS_ACCOUNT_URL = "/v2/SynergiaAccounts/fresh/" // + login
|
||||
const val LIBRUS_ACCOUNTS_URL = "/v2/SynergiaAccounts"
|
||||
const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login
|
||||
const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts"
|
||||
|
||||
/** https://api.librus.pl/2.0 */
|
||||
const val LIBRUS_API_URL = "https://api.librus.pl/2.0"
|
||||
@ -95,7 +95,16 @@ const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia"
|
||||
const val VULCAN_API_APP_VERSION = "20.5.1.470"
|
||||
const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06"
|
||||
const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB"
|
||||
val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}"
|
||||
const val VULCAN_HEBE_USER_AGENT = "Dart/2.10 (dart:io)"
|
||||
const val VULCAN_HEBE_APP_NAME = "DzienniczekPlus 2.0"
|
||||
const val VULCAN_HEBE_APP_VERSION = "21.02.09 (G)"
|
||||
private const val VULCAN_API_DEVICE_NAME_PREFIX = "Szkolny.eu "
|
||||
private const val VULCAN_API_DEVICE_NAME_SUFFIX = " - nie usuwać"
|
||||
val VULCAN_API_DEVICE_NAME by lazy {
|
||||
val base = "$VULCAN_API_DEVICE_NAME_PREFIX${Build.MODEL}"
|
||||
val baseMaxLength = 50 - VULCAN_API_DEVICE_NAME_SUFFIX.length
|
||||
base.take(baseMaxLength) + VULCAN_API_DEVICE_NAME_SUFFIX
|
||||
}
|
||||
|
||||
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"
|
||||
@ -116,9 +125,26 @@ const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/
|
||||
const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik"
|
||||
const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
|
||||
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get"
|
||||
const val VULCAN_HEBE_ENDPOINT_REGISTER_NEW = "api/mobile/register/new"
|
||||
const val VULCAN_HEBE_ENDPOINT_MAIN = "api/mobile/register/hebe"
|
||||
const val VULCAN_HEBE_ENDPOINT_PUSH_ALL = "api/mobile/push/all"
|
||||
const val VULCAN_HEBE_ENDPOINT_TIMETABLE = "api/mobile/schedule"
|
||||
const val VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES = "api/mobile/schedule/changes"
|
||||
const val VULCAN_HEBE_ENDPOINT_ADDRESSBOOK = "api/mobile/addressbook"
|
||||
const val VULCAN_HEBE_ENDPOINT_EXAMS = "api/mobile/exam"
|
||||
const val VULCAN_HEBE_ENDPOINT_GRADES = "api/mobile/grade"
|
||||
const val VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY = "api/mobile/grade/summary"
|
||||
const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework"
|
||||
const val VULCAN_HEBE_ENDPOINT_NOTICES = "api/mobile/note"
|
||||
const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES_SEND = "api/mobile/message"
|
||||
const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky"
|
||||
|
||||
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
|
||||
|
||||
const val PODLASIE_API_VERSION = "1.0.31"
|
||||
const val PODLASIE_API_VERSION = "1.0.62"
|
||||
const val PODLASIE_API_URL = "https://cpdklaser.zeto.bialystok.pl/api"
|
||||
const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia"
|
||||
const val PODLASIE_API_LOGOUT_DEVICES_ENDPOINT = "/wyczyscUrzadzenia"
|
||||
|
@ -170,6 +170,8 @@ 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_API_DEPRECATED = 390
|
||||
|
||||
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
|
||||
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
|
||||
@ -229,5 +231,6 @@ 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
|
||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLogi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
|
||||
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
|
||||
|
||||
@ -98,11 +99,13 @@ val mobidziennikLoginMethods = listOf(
|
||||
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_API = 500
|
||||
const val LOGIN_METHOD_VULCAN_HEBE = 600
|
||||
val vulcanLoginMethods = listOf(
|
||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
|
||||
.withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") }
|
||||
@ -117,10 +120,16 @@ val vulcanLoginMethods = listOf(
|
||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/
|
||||
|
||||
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_MAIN else LOGIN_METHOD_NOT_NEEDED
|
||||
.withIsPossible { _, loginStore ->
|
||||
loginStore.mode == LOGIN_MODE_VULCAN_API
|
||||
}
|
||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
|
||||
|
||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
|
||||
.withIsPossible { _, loginStore ->
|
||||
loginStore.mode != LOGIN_MODE_VULCAN_API
|
||||
}
|
||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
|
||||
)
|
||||
|
||||
val idziennikLoginMethods = listOf(
|
||||
|
@ -12,6 +12,10 @@ object Regexes {
|
||||
"""color: (\w+);?""".toRegex()
|
||||
}
|
||||
|
||||
val NOT_DIGITS by lazy {
|
||||
"""[^0-9]""".toRegex()
|
||||
}
|
||||
|
||||
|
||||
|
||||
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
|
||||
@ -80,6 +84,15 @@ object Regexes {
|
||||
val MOBIDZIENNIK_ATTENDANCE_ENTRIES by lazy {
|
||||
"""font-size:.+?class=".*?">(.*?)</td>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_ATTENDANCE_COLUMNS by lazy {
|
||||
"""<tr><td class="border-right1".+?/td>(.+?)</tr>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_ATTENDANCE_COLUMN by lazy {
|
||||
"""(<td.+?>)(.*?)</td>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_ATTENDANCE_COLUMN_SPAN by lazy {
|
||||
"""colspan="(\d+)"""".toRegex()
|
||||
}
|
||||
val MOBIDZIENNIK_ATTENDANCE_RANGE by lazy {
|
||||
"""<span>([0-9:]+) - .+? (.+?)</span></a>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
@ -143,7 +156,7 @@ object Regexes {
|
||||
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_PERMISSIONS by lazy {
|
||||
"""permissions: '([A-z0-9/=+\-_]+?)'""".toRegex()
|
||||
"""permissions: '([A-z0-9/=+\-_|]+?)'""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_SYMBOL_VALIDATE by lazy {
|
||||
"""[A-z0-9]+""".toRegex(IGNORE_CASE)
|
||||
|
@ -5,8 +5,8 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.Edudziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.Idziennik
|
||||
@ -15,9 +15,11 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.Podlasie
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.Template
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.Vulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
@ -25,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
|
||||
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
|
||||
companion object {
|
||||
@ -71,10 +74,51 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
private var edziennikInterface: EdziennikInterface? = null
|
||||
|
||||
internal fun run(app: App, taskCallback: EdziennikCallback) {
|
||||
if (profile?.archived == true) {
|
||||
taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED))
|
||||
return
|
||||
profile?.let { profile ->
|
||||
if (profile.archived) {
|
||||
d(TAG, "The profile $profileId is archived")
|
||||
taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED))
|
||||
return
|
||||
}
|
||||
else if (profile.shouldArchive()) {
|
||||
d(TAG, "The profile $profileId's year ended on ${profile.dateYearEnd}, archiving")
|
||||
ProfileArchiver(app, profile)
|
||||
}
|
||||
if (profile.isBeforeYear()) {
|
||||
d(TAG, "The profile $profileId's school year has not started yet; aborting sync")
|
||||
cancel()
|
||||
taskCallback.onCompleted()
|
||||
return
|
||||
}
|
||||
|
||||
profile.registerName?.let { registerName ->
|
||||
var status = app.config.sync.registerAvailability[registerName]
|
||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
||||
val api = SzkolnyApi(app)
|
||||
api.runCatching({
|
||||
val availability = getRegisterAvailability()
|
||||
app.config.sync.registerAvailability = availability
|
||||
status = availability[registerName]
|
||||
}, onError = {
|
||||
taskCallback.onError(it.toApiError(TAG))
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
if (status?.available != true
|
||||
|| status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) {
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
||||
EventBus.getDefault().postSticky(
|
||||
RegisterAvailabilityEvent(app.config.sync.registerAvailability)
|
||||
)
|
||||
}
|
||||
cancel()
|
||||
taskCallback.onCompleted()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edziennikInterface = when (loginStore.type) {
|
||||
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
||||
@ -108,6 +152,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
d(TAG, "Task ${toString()} cancelling...")
|
||||
edziennikInterface?.cancel()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-8-25.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik
|
||||
|
||||
import android.content.Intent
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.Intent
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class ProfileArchiver(val app: App, val profile: Profile) {
|
||||
companion object {
|
||||
private const val TAG = "ProfileArchiver"
|
||||
}
|
||||
|
||||
init {
|
||||
if (profile.archiveId == null)
|
||||
profile.archiveId = profile.id
|
||||
d(TAG, "Processing ${profile.name}#${profile.id}, archiveId = ${profile.archiveId}")
|
||||
|
||||
profile.archived = true
|
||||
app.db.profileDao().add(profile)
|
||||
//app.db.metadataDao().setAllSeen(profile.id, true)
|
||||
app.db.notificationDao().clear(profile.id)
|
||||
app.db.endpointTimerDao().clear(profile.id)
|
||||
d(TAG, "Archived profile ${profile.id} saved")
|
||||
profile.archived = false
|
||||
|
||||
// guess the nearest school year
|
||||
val today = Date.getToday()
|
||||
profile.studentSchoolYearStart = when {
|
||||
today.month <= profile.dateYearEnd.month -> today.year - 1
|
||||
else -> today.year
|
||||
}
|
||||
|
||||
// set default semester dates
|
||||
profile.dateSemester1Start = Date(profile.studentSchoolYearStart, 9, 1)
|
||||
profile.dateSemester2Start = Date(profile.studentSchoolYearStart + 1, 2, 1)
|
||||
profile.dateYearEnd = Date(profile.studentSchoolYearStart + 1, 6, 30)
|
||||
|
||||
val oldId = profile.id
|
||||
val newId = (app.db.profileDao().lastId ?: profile.id) + 1
|
||||
profile.id = newId
|
||||
profile.subname = "Nowy rok szkolny - ${profile.studentSchoolYearStart}"
|
||||
profile.studentClassName = null
|
||||
|
||||
d(TAG, "New profile ID for ${profile.name}: ${profile.id}")
|
||||
|
||||
when (profile.loginStoreType) {
|
||||
LOGIN_TYPE_LIBRUS -> {
|
||||
profile.removeStudentData("isPremium")
|
||||
profile.removeStudentData("pushDeviceId")
|
||||
profile.removeStudentData("startPointsSemester1")
|
||||
profile.removeStudentData("startPointsSemester2")
|
||||
profile.removeStudentData("enablePointGrades")
|
||||
profile.removeStudentData("enableDescriptiveGrades")
|
||||
}
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> {
|
||||
|
||||
}
|
||||
LOGIN_TYPE_VULCAN -> {
|
||||
// DataVulcan.isApiLoginValid() returns false so it will update the semester
|
||||
profile.removeStudentData("currentSemesterEndDate")
|
||||
profile.removeStudentData("studentSemesterId")
|
||||
profile.removeStudentData("studentSemesterNumber")
|
||||
profile.removeStudentData("semester1Id")
|
||||
profile.removeStudentData("semester2Id")
|
||||
profile.removeStudentData("studentClassId")
|
||||
}
|
||||
LOGIN_TYPE_IDZIENNIK -> {
|
||||
profile.removeStudentData("schoolYearId")
|
||||
}
|
||||
LOGIN_TYPE_EDUDZIENNIK -> {
|
||||
|
||||
}
|
||||
LOGIN_TYPE_PODLASIE -> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
d(TAG, "Processed student data: ${profile.studentData}")
|
||||
|
||||
app.db.profileDao().add(profile)
|
||||
|
||||
if (app.profileId == oldId) {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_MAIN,
|
||||
"profileId" to newId
|
||||
)
|
||||
app.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -110,7 +110,6 @@ class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStor
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -133,7 +133,6 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -70,6 +70,14 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
||||
data.webSelectedRegister = registerId
|
||||
}
|
||||
|
||||
// for profiles created after archiving
|
||||
data.schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let {
|
||||
it[1].toIntOrNull()
|
||||
} ?: data.schoolYearId
|
||||
data.profile?.studentClassName = Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text)
|
||||
.firstOrNull { it[1].toIntOrNull() == data.registerId }
|
||||
?.let { "${it[5]} ${it[6]}" } ?: data.profile?.studentClassName
|
||||
|
||||
data.profile?.let { profile ->
|
||||
Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also {
|
||||
val number = it[1].toIntOrNull() ?: return@also
|
||||
|
@ -157,7 +157,6 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -36,11 +36,14 @@ class LibrusRecaptchaHelper(
|
||||
}
|
||||
|
||||
private var timeout: Job? = null
|
||||
private var timedOut = false
|
||||
|
||||
inner class WebViewClient : android.webkit.WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
timeout?.cancel()
|
||||
onSuccess(url)
|
||||
if (!timedOut) {
|
||||
onSuccess(url)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -50,6 +53,7 @@ class LibrusRecaptchaHelper(
|
||||
webView.loadDataWithBaseURL(url, html, "text/html", "UTF-8", null)
|
||||
}
|
||||
timeout = startCoroutineTimer(delayMillis = 10000L) {
|
||||
timedOut = true
|
||||
onTimeout()
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
override fun onSuccess(text: String, response: Response) {
|
||||
val location = response.headers().get("Location")
|
||||
if (location != null) {
|
||||
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||
val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||
when {
|
||||
authMatcher.find() -> {
|
||||
accessToken(authMatcher.group(1), null)
|
||||
@ -127,7 +127,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.callback(object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response) {
|
||||
val location = response.headers()?.get("Location")
|
||||
if (location == "http://localhost/bar?command=close") {
|
||||
if (location == "$LIBRUS_REDIRECT_URL?command=close") {
|
||||
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
|
@ -130,7 +130,6 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -44,7 +44,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
|
||||
dataDays.remove(date.value)
|
||||
|
||||
val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5] }?.id ?: -1
|
||||
val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5].trim() }?.id ?: -1
|
||||
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.id ?: -1
|
||||
val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
|
||||
val classroom = lesson[11]
|
||||
|
@ -91,8 +91,11 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
|
||||
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_TABLE.findAll(text).forEach { tableResult ->
|
||||
val table = tableResult[1]
|
||||
|
||||
val lessonDates = mutableListOf<Date>()
|
||||
val entries = mutableListOf<String>()
|
||||
val ranges = mutableListOf<MatchResult?>()
|
||||
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON_COUNT.findAll(table).forEach {
|
||||
val date = Date.fromY_m_d(it[1])
|
||||
for (i in 0 until (it[2].toIntOrNull() ?: 0)) {
|
||||
@ -101,102 +104,52 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
|
||||
}
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_ENTRIES.findAll(table).mapTo(entries) { it[1] }
|
||||
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_COLUMNS.findAll(table).forEach { columns ->
|
||||
var index = 0
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_COLUMN.findAll(columns[1]).forEach { column ->
|
||||
if (column[1].contains("colspan")) {
|
||||
val colspan =
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_COLUMN_SPAN.find(column[1])
|
||||
?.get(1)
|
||||
?.toIntOrNull() ?: 0
|
||||
entries.addAll(index, List(colspan) { "" })
|
||||
ranges.addAll(List(colspan) { null })
|
||||
index += colspan
|
||||
}
|
||||
else {
|
||||
val range = Regexes.MOBIDZIENNIK_ATTENDANCE_RANGE.find(column[2])
|
||||
ranges.add(range)
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dateIterator = lessonDates.iterator()
|
||||
val entriesIterator = entries.iterator()
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_RANGE.findAll(table).let { ranges ->
|
||||
val count = ranges.count()
|
||||
// verify the lesson count is the same as dates & entries
|
||||
if (count != lessonDates.count() || count != entries.count())
|
||||
|
||||
val count = ranges.count()
|
||||
// verify the lesson count is the same as dates & entries
|
||||
if (count != lessonDates.count() || count != entries.count())
|
||||
return@forEach
|
||||
ranges.forEach { range ->
|
||||
val lessonDate = dateIterator.next()
|
||||
val entry = entriesIterator.next()
|
||||
if (range == null || entry.isBlank())
|
||||
return@forEach
|
||||
ranges.forEach { range ->
|
||||
val lessonDate = dateIterator.next()
|
||||
var entry = entriesIterator.next()
|
||||
if (entry.isBlank())
|
||||
return@forEach
|
||||
val startTime = Time.fromH_m(range[1])
|
||||
val startTime = Time.fromH_m(range[1])
|
||||
|
||||
range[2].split(" / ").mapNotNull { Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON.find(it) }.forEachIndexed { index, lesson ->
|
||||
val topic = lesson[1].substringAfter(" - ", missingDelimiterValue = "").takeIf { it.isNotBlank() }
|
||||
if (topic?.startsWith("Lekcja odwołana: ") == true || entry.isEmpty())
|
||||
return@forEachIndexed
|
||||
val subjectName = lesson[1].substringBefore(" - ")
|
||||
//val team = lesson[3]
|
||||
val teacherName = lesson[3].fixName()
|
||||
|
||||
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1
|
||||
val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1
|
||||
|
||||
var typeSymbol = ""
|
||||
for (symbol in typeSymbols) {
|
||||
if (entry.startsWith(symbol) && symbol.length > typeSymbol.length)
|
||||
typeSymbol = symbol
|
||||
}
|
||||
entry = entry.removePrefix(typeSymbol)
|
||||
|
||||
var isCounted = true
|
||||
val baseType = when (typeSymbol) {
|
||||
"." -> TYPE_PRESENT
|
||||
"|" -> TYPE_ABSENT
|
||||
"+" -> TYPE_ABSENT_EXCUSED
|
||||
"s" -> TYPE_BELATED
|
||||
"z" -> TYPE_RELEASED
|
||||
else -> {
|
||||
isCounted = false
|
||||
when (typeSymbol) {
|
||||
"e" -> TYPE_PRESENT_CUSTOM
|
||||
"en" -> TYPE_ABSENT
|
||||
"ep" -> TYPE_PRESENT_CUSTOM
|
||||
else -> TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
val typeName = types?.get(typeSymbol) ?: ""
|
||||
val typeColor = when (typeSymbol) {
|
||||
"e" -> 0xff673ab7
|
||||
"en" -> 0xffec407a
|
||||
"ep" -> 0xff4caf50
|
||||
else -> null
|
||||
}?.toInt()
|
||||
|
||||
val typeShort = if (isCounted)
|
||||
data.app.attendanceManager.getTypeShort(baseType)
|
||||
else
|
||||
typeSymbol
|
||||
|
||||
val semester = data.profile?.dateToSemester(lessonDate) ?: 1
|
||||
|
||||
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson[0].hashCode() and 0xFFFF) + index
|
||||
|
||||
val attendanceObject = Attendance(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
baseType = baseType,
|
||||
typeName = typeName,
|
||||
typeShort = typeShort,
|
||||
typeSymbol = typeSymbol,
|
||||
typeColor = typeColor,
|
||||
date = lessonDate,
|
||||
startTime = startTime,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
).also {
|
||||
it.lessonTopic = topic
|
||||
it.isCounted = isCounted
|
||||
}
|
||||
|
||||
data.attendanceList.add(attendanceObject)
|
||||
if (baseType != TYPE_PRESENT) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_ATTENDANCE,
|
||||
id,
|
||||
data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN,
|
||||
data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN
|
||||
))
|
||||
}
|
||||
}
|
||||
range[2].split(" / ").mapNotNull {
|
||||
Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON.find(it)
|
||||
}.forEachIndexed { index, lesson ->
|
||||
processEntry(
|
||||
index,
|
||||
lesson,
|
||||
lessonDate,
|
||||
startTime,
|
||||
entry,
|
||||
types,
|
||||
typeSymbols
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,4 +159,97 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun processEntry(
|
||||
index: Int,
|
||||
lesson: MatchResult,
|
||||
lessonDate: Date,
|
||||
startTime: Time,
|
||||
entry: String,
|
||||
types: Map<String?, String?>?,
|
||||
typeSymbols: List<String>
|
||||
) {
|
||||
var entry = entry
|
||||
|
||||
val topic = lesson[1].substringAfter(" - ", missingDelimiterValue = "").takeIf { it.isNotBlank() }
|
||||
if (topic?.startsWith("Lekcja odwołana: ") == true || entry.isEmpty())
|
||||
return
|
||||
val subjectName = lesson[1].substringBefore(" - ")
|
||||
//val team = lesson[3]
|
||||
val teacherName = lesson[3].fixName()
|
||||
|
||||
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1
|
||||
val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1
|
||||
|
||||
var typeSymbol = ""
|
||||
for (symbol in typeSymbols) {
|
||||
if (entry.startsWith(symbol) && symbol.length > typeSymbol.length)
|
||||
typeSymbol = symbol
|
||||
}
|
||||
entry = entry.removePrefix(typeSymbol)
|
||||
|
||||
var isCounted = true
|
||||
val baseType = when (typeSymbol) {
|
||||
"." -> TYPE_PRESENT
|
||||
"|" -> TYPE_ABSENT
|
||||
"+" -> TYPE_ABSENT_EXCUSED
|
||||
"s" -> TYPE_BELATED
|
||||
"z" -> TYPE_RELEASED
|
||||
else -> {
|
||||
isCounted = false
|
||||
when (typeSymbol) {
|
||||
"e" -> TYPE_PRESENT_CUSTOM
|
||||
"en" -> TYPE_ABSENT
|
||||
"ep" -> TYPE_PRESENT_CUSTOM
|
||||
else -> TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
val typeName = types?.get(typeSymbol) ?: ""
|
||||
val typeColor = when (typeSymbol) {
|
||||
"e" -> 0xff673ab7
|
||||
"en" -> 0xffec407a
|
||||
"ep" -> 0xff4caf50
|
||||
else -> null
|
||||
}?.toInt()
|
||||
|
||||
val typeShort = if (isCounted)
|
||||
data.app.attendanceManager.getTypeShort(baseType)
|
||||
else
|
||||
typeSymbol
|
||||
|
||||
val semester = data.profile?.dateToSemester(lessonDate) ?: 1
|
||||
|
||||
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson[0].hashCode() and 0xFFFF) + index
|
||||
|
||||
val attendanceObject = Attendance(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
baseType = baseType,
|
||||
typeName = typeName,
|
||||
typeShort = typeShort,
|
||||
typeSymbol = typeSymbol,
|
||||
typeColor = typeColor,
|
||||
date = lessonDate,
|
||||
startTime = startTime,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
).also {
|
||||
it.lessonTopic = topic
|
||||
it.isCounted = isCounted
|
||||
}
|
||||
|
||||
data.attendanceList.add(attendanceObject)
|
||||
if (baseType != TYPE_PRESENT) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_ATTENDANCE,
|
||||
id,
|
||||
data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN,
|
||||
data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,20 +56,21 @@ class MobidziennikWebGetRecipientList(override val data: DataMobidziennik,
|
||||
}
|
||||
|
||||
private fun processRecipient(listType: Int, listName: String, recipient: JsonObject) {
|
||||
val id = recipient.getLong("id") ?: -1
|
||||
val id = recipient.getString("id") ?: return
|
||||
val idLong = id.replace(Regexes.NOT_DIGITS, "").toLongOrNull() ?: return
|
||||
// get teacher by ID or create it
|
||||
val teacher = data.teacherList[id] ?: Teacher(data.profileId, id).apply {
|
||||
val teacher = data.teacherList[idLong] ?: Teacher(data.profileId, idLong).apply {
|
||||
val fullName = recipient.getString("nazwa")?.fixName()
|
||||
name = fullName ?: ""
|
||||
fullName?.splitName()?.let {
|
||||
name = it.second
|
||||
surname = it.first
|
||||
}
|
||||
data.teacherList[id] = this
|
||||
data.teacherList[idLong] = this
|
||||
}
|
||||
|
||||
teacher.apply {
|
||||
loginId = id.toString()
|
||||
loginId = id
|
||||
when (listType) {
|
||||
1 -> setTeacherType(Teacher.TYPE_PRINCIPAL)
|
||||
2 -> setTeacherType(Teacher.TYPE_TEACHER)
|
||||
|
@ -134,7 +134,6 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
override fun cancel() {
|
||||
Utils.d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE
|
||||
import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_LOGOUT_DEVICES_ENDPOINT
|
||||
import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_USER_ENDPOINT
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi
|
||||
@ -22,50 +23,62 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) {
|
||||
private val api = PodlasieApi(data, null)
|
||||
|
||||
init {
|
||||
PodlasieLoginApi(data) {
|
||||
doLogin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doLogin() {
|
||||
val loginStoreId = data.loginStore.id
|
||||
val loginStoreType = LOGIN_TYPE_PODLASIE
|
||||
|
||||
PodlasieLoginApi(data) {
|
||||
api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
|
||||
val uuid = json.getString("Uuid")
|
||||
val login = json.getString("Login")
|
||||
val firstName = json.getString("FirstName")
|
||||
val lastName = json.getString("LastName")
|
||||
val studentNameLong = "$firstName $lastName".fixName()
|
||||
val studentNameShort = studentNameLong.getShortName()
|
||||
val schoolName = json.getString("SchoolName")
|
||||
val className = json.getString("SchoolClass")
|
||||
val schoolYear = json.getString("ActualSchoolYear")?.replace(' ', '/')
|
||||
val semester = json.getString("ActualTermShortcut")?.length
|
||||
val apiUrl = json.getString("URL")
|
||||
|
||||
val profile = Profile(
|
||||
loginStoreId,
|
||||
loginStoreId,
|
||||
loginStoreType,
|
||||
studentNameLong,
|
||||
login,
|
||||
studentNameLong,
|
||||
studentNameShort,
|
||||
null
|
||||
).apply {
|
||||
studentData["studentId"] = uuid
|
||||
studentData["studentLogin"] = login
|
||||
studentData["schoolName"] = schoolName
|
||||
studentData["className"] = className
|
||||
studentData["schoolYear"] = schoolYear
|
||||
studentData["currentSemester"] = semester ?: 1
|
||||
studentData["apiUrl"] = apiUrl
|
||||
|
||||
schoolYear?.split('/')?.get(0)?.toInt()?.let {
|
||||
studentSchoolYearStart = it
|
||||
}
|
||||
studentClassName = className
|
||||
}
|
||||
|
||||
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), data.loginStore))
|
||||
onSuccess()
|
||||
if (data.loginStore.getLoginData("logoutDevices", false)) {
|
||||
data.loginStore.removeLoginData("logoutDevices")
|
||||
api.apiGet(TAG, PODLASIE_API_LOGOUT_DEVICES_ENDPOINT) {
|
||||
doLogin()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
|
||||
val uuid = json.getString("Uuid")
|
||||
val login = json.getString("Login")
|
||||
val firstName = json.getString("FirstName")
|
||||
val lastName = json.getString("LastName")
|
||||
val studentNameLong = "$firstName $lastName".fixName()
|
||||
val studentNameShort = studentNameLong.getShortName()
|
||||
val schoolName = json.getString("SchoolName")
|
||||
val className = json.getString("SchoolClass")
|
||||
val schoolYear = json.getString("ActualSchoolYear")?.replace(' ', '/')
|
||||
val semester = json.getString("ActualTermShortcut")?.length
|
||||
val apiUrl = json.getString("URL")
|
||||
|
||||
val profile = Profile(
|
||||
loginStoreId,
|
||||
loginStoreId,
|
||||
loginStoreType,
|
||||
studentNameLong,
|
||||
login,
|
||||
studentNameLong,
|
||||
studentNameShort,
|
||||
null
|
||||
).apply {
|
||||
studentData["studentId"] = uuid
|
||||
studentData["studentLogin"] = login
|
||||
studentData["schoolName"] = schoolName
|
||||
studentData["className"] = className
|
||||
studentData["schoolYear"] = schoolYear
|
||||
studentData["currentSemester"] = semester ?: 1
|
||||
studentData["apiUrl"] = apiUrl
|
||||
|
||||
schoolYear?.split('/')?.get(0)?.toInt()?.let {
|
||||
studentSchoolYearStart = it
|
||||
}
|
||||
studentClassName = className
|
||||
}
|
||||
|
||||
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), data.loginStore))
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,6 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -4,39 +4,49 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan
|
||||
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
|
||||
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.LOGIN_MODE_VULCAN_API
|
||||
import pl.szczodrzynski.edziennik.data.api.models.Data
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.values
|
||||
import pl.szczodrzynski.fslogin.realm.RealmData
|
||||
|
||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||
|
||||
fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix()
|
||||
&& webAuthCookie.isNotNullNorEmpty()
|
||||
&& webHost.isNotNullNorEmpty()
|
||||
&& webType.isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
fun isWebMainLoginValid() = symbol.isNotNullNorEmpty()
|
||||
&& (webExpiryTime[symbol]?.toLongOrNull() ?: 0) - 30 > currentTimeUnix()
|
||||
&& webAuthCookie[symbol].isNotNullNorEmpty()
|
||||
&& webRealmData != null
|
||||
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||
&& apiPrivateKey[symbol].isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
fun isHebeLoginValid() = hebePublicKey.isNotNullNorEmpty()
|
||||
&& hebePrivateKey.isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
|
||||
override fun satisfyLoginMethods() {
|
||||
loginMethods.clear()
|
||||
if (isWebMainLoginValid()) {
|
||||
loginMethods += LOGIN_METHOD_VULCAN_WEB_MAIN
|
||||
}
|
||||
if (isApiLoginValid()) {
|
||||
loginMethods += LOGIN_METHOD_VULCAN_API
|
||||
}
|
||||
if (isHebeLoginValid()) {
|
||||
loginMethods += LOGIN_METHOD_VULCAN_HEBE
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// during the first sync `profile.studentClassName` is already set
|
||||
if (teamList.values().none { it.type == Team.TYPE_CLASS }) {
|
||||
if (loginStore.mode == LOGIN_MODE_VULCAN_API
|
||||
&& teamList.values().none { it.type == Team.TYPE_CLASS }) {
|
||||
profile?.studentClassName?.also { name ->
|
||||
val id = Utils.crc16(name.toByteArray()).toLong()
|
||||
|
||||
@ -55,6 +65,17 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
|
||||
override fun generateUserCode() = "$schoolCode:$studentId"
|
||||
|
||||
fun buildDeviceId(): String {
|
||||
val deviceId = app.deviceId.padStart(16, '0')
|
||||
val loginStoreId = loginStore.id.toString(16).padStart(4, '0')
|
||||
val symbol = symbol?.crc16()?.toString(16)?.take(2) ?: "00"
|
||||
return deviceId.substring(0..7) +
|
||||
"-" + deviceId.substring(8..11) +
|
||||
"-" + deviceId.substring(12..15) +
|
||||
"-" + loginStoreId +
|
||||
"-" + symbol + "6f72616e7a"
|
||||
}
|
||||
|
||||
/**
|
||||
* A UONET+ client symbol.
|
||||
*
|
||||
@ -139,6 +160,16 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 }
|
||||
set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = value }
|
||||
|
||||
private var mStudentUnitId: Int? = null
|
||||
var studentUnitId: Int
|
||||
get() { mStudentUnitId = mStudentUnitId ?: profile?.getStudentData("studentUnitId", 0); return mStudentUnitId ?: 0 }
|
||||
set(value) { profile?.putStudentData("studentUnitId", value) ?: return; mStudentUnitId = value }
|
||||
|
||||
private var mStudentConstituentId: Int? = null
|
||||
var studentConstituentId: Int
|
||||
get() { mStudentConstituentId = mStudentConstituentId ?: profile?.getStudentData("studentConstituentId", 0); return mStudentConstituentId ?: 0 }
|
||||
set(value) { profile?.putStudentData("studentConstituentId", value) ?: return; mStudentConstituentId = value }
|
||||
|
||||
private var mSemester1Id: Int? = null
|
||||
var semester1Id: Int
|
||||
get() { mSemester1Id = mSemester1Id ?: profile?.getStudentData("semester1Id", 0); return mSemester1Id ?: 0 }
|
||||
@ -203,6 +234,32 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
get() { mApiPrivateKey = mApiPrivateKey ?: loginStore.getLoginData("apiPrivateKey", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPrivateKey ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("apiPrivateKey", app.gson.toJson(value)); mApiPrivateKey = value }
|
||||
|
||||
/* _ _ _ _____ _____
|
||||
| | | | | | /\ | __ \_ _|
|
||||
| |__| | ___| |__ ___ / \ | |__) || |
|
||||
| __ |/ _ \ '_ \ / _ \ / /\ \ | ___/ | |
|
||||
| | | | __/ |_) | __/ / ____ \| | _| |_
|
||||
|_| |_|\___|_.__/ \___| /_/ \_\_| |____*/
|
||||
private var mHebePublicKey: String? = null
|
||||
var hebePublicKey: String?
|
||||
get() { mHebePublicKey = mHebePublicKey ?: loginStore.getLoginData("hebePublicKey", null); return mHebePublicKey }
|
||||
set(value) { loginStore.putLoginData("hebePublicKey", value); mHebePublicKey = value }
|
||||
|
||||
private var mHebePrivateKey: String? = null
|
||||
var hebePrivateKey: String?
|
||||
get() { mHebePrivateKey = mHebePrivateKey ?: loginStore.getLoginData("hebePrivateKey", null); return mHebePrivateKey }
|
||||
set(value) { loginStore.putLoginData("hebePrivateKey", value); mHebePrivateKey = value }
|
||||
|
||||
private var mHebePublicHash: String? = null
|
||||
var hebePublicHash: String?
|
||||
get() { mHebePublicHash = mHebePublicHash ?: loginStore.getLoginData("hebePublicHash", null); return mHebePublicHash }
|
||||
set(value) { loginStore.putLoginData("hebePublicHash", value); mHebePublicHash = value }
|
||||
|
||||
private var mHebeContext: String? = null
|
||||
var hebeContext: String?
|
||||
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
|
||||
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
|
||||
|
||||
val apiUrl: String?
|
||||
get() {
|
||||
val url = when (apiToken[symbol]?.substring(0, 3)) {
|
||||
@ -219,6 +276,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
"P01" -> "http://efeb-komunikacja.pro-hudson.win.vulcan.pl"
|
||||
"P02" -> "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl"
|
||||
"P90" -> "http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl"
|
||||
"KO1" -> "https://uonetplus-komunikacja.eduportal.koszalin.pl"
|
||||
"FK1", "FS1" -> "http://api.fakelog.cf"
|
||||
"SZ9" -> "http://hack.szkolny.eu"
|
||||
else -> null
|
||||
@ -226,7 +284,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
return if (url != null) "$url/$symbol/" else loginStore.getLoginData("apiUrl", null)
|
||||
}
|
||||
|
||||
val fullApiUrl: String?
|
||||
val fullApiUrl: String
|
||||
get() {
|
||||
return "$apiUrl$schoolSymbol/"
|
||||
}
|
||||
@ -239,49 +297,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
\/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
/**
|
||||
* Federation Services login type.
|
||||
* This might be one of: cufs, adfs, adfslight.
|
||||
*/
|
||||
var webType: String?
|
||||
get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType }
|
||||
set(value) { loginStore.putLoginData("webType", value); mWebType = value }
|
||||
private var mWebType: String? = null
|
||||
var webRealmData: RealmData?
|
||||
get() { mWebRealmData = mWebRealmData ?: loginStore.getLoginData("webRealmData", JsonObject()).let {
|
||||
app.gson.fromJson(it, RealmData::class.java)
|
||||
}; return mWebRealmData }
|
||||
set(value) { loginStore.putLoginData("webRealmData", app.gson.toJsonTree(value) as JsonObject); mWebRealmData = value }
|
||||
private var mWebRealmData: RealmData? = null
|
||||
|
||||
/**
|
||||
* Web server providing the federation services login.
|
||||
* If this is present, WEB_MAIN login is considered as available.
|
||||
*/
|
||||
var webHost: String?
|
||||
get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost }
|
||||
set(value) { loginStore.putLoginData("webHost", value); mWebHost = value }
|
||||
private var mWebHost: String? = null
|
||||
|
||||
/**
|
||||
* An ID used in ADFS & ADFSLight login types.
|
||||
*/
|
||||
var webAdfsId: String?
|
||||
get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId }
|
||||
set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value }
|
||||
private var mWebAdfsId: String? = null
|
||||
|
||||
/**
|
||||
* A domain override for ADFS Light.
|
||||
*/
|
||||
var webAdfsDomain: String?
|
||||
get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain }
|
||||
set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value }
|
||||
private var mWebAdfsDomain: String? = null
|
||||
|
||||
var webIsHttpCufs: Boolean
|
||||
get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false }
|
||||
set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value }
|
||||
private var mWebIsHttpCufs: Boolean? = null
|
||||
|
||||
var webIsScopedAdfs: Boolean
|
||||
get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false }
|
||||
set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value }
|
||||
private var mWebIsScopedAdfs: Boolean? = null
|
||||
val webHost
|
||||
get() = webRealmData?.host
|
||||
|
||||
var webEmail: String?
|
||||
get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail }
|
||||
@ -301,24 +325,24 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
* If the time passes, the certificate needs to be POSTed again (if valid)
|
||||
* or re-generated.
|
||||
*/
|
||||
var webExpiryTime: Long
|
||||
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
|
||||
set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value }
|
||||
private var mWebExpiryTime: Long? = null
|
||||
var webExpiryTime: Map<String, String?> = mapOf()
|
||||
get() { mWebExpiryTime = mWebExpiryTime ?: loginStore.getLoginData("webExpiryTime", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebExpiryTime ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webExpiryTime", app.gson.toJson(value)); mWebExpiryTime = value }
|
||||
private var mWebExpiryTime: Map<String, String?>? = null
|
||||
|
||||
/**
|
||||
* EfebSsoAuthCookie retrieved after posting a certificate
|
||||
*/
|
||||
var webAuthCookie: String?
|
||||
get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie }
|
||||
set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value }
|
||||
private var mWebAuthCookie: String? = null
|
||||
var webAuthCookie: Map<String, String?> = mapOf()
|
||||
get() { mWebAuthCookie = mWebAuthCookie ?: loginStore.getLoginData("webAuthCookie", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebAuthCookie ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webAuthCookie", app.gson.toJson(value)); mWebAuthCookie = value }
|
||||
private var mWebAuthCookie: Map<String, String?>? = null
|
||||
|
||||
/**
|
||||
* Permissions needed to get JSONs from home page
|
||||
*/
|
||||
var webPermissions: String?
|
||||
get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions }
|
||||
set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value }
|
||||
private var mWebPermissions: String? = null
|
||||
var webPermissions: Map<String, String?> = mapOf()
|
||||
get() { mWebPermissions = mWebPermissions ?: loginStore.getLoginData("webPermissions", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebPermissions ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webPermissions", app.gson.toJson(value)); mWebPermissions = value }
|
||||
private var mWebPermissions: Map<String, String?>? = null
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanData
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiAttachments
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiMessagesChangeStatus
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiSendMessage
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMessagesChangeStatus
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeSendMessage
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin.VulcanFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
|
||||
@ -91,6 +93,20 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun getMessage(message: MessageFull) {
|
||||
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
|
||||
login(LOGIN_METHOD_VULCAN_HEBE) {
|
||||
if (message.seen) {
|
||||
EventBus.getDefault().postSticky(MessageGetEvent(message))
|
||||
completed()
|
||||
return@login
|
||||
}
|
||||
VulcanHebeMessagesChangeStatus(data, message) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
login(LOGIN_METHOD_VULCAN_API) {
|
||||
if (message.attachmentIds != null) {
|
||||
VulcanApiMessagesChangeStatus(data, message) {
|
||||
@ -120,6 +136,15 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun sendMessage(recipients: List<Teacher>, subject: String, text: String) {
|
||||
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
|
||||
login(LOGIN_METHOD_VULCAN_HEBE) {
|
||||
VulcanHebeSendMessage(data, recipients, subject, text) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
login(LOGIN_METHOD_VULCAN_API) {
|
||||
VulcanApiSendMessage(data, recipients, subject, text) {
|
||||
completed()
|
||||
@ -175,6 +200,14 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun getEvent(eventFull: EventFull) {
|
||||
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
|
||||
eventFull.homeworkBody = ""
|
||||
|
||||
EventBus.getDefault().postSticky(EventGetEvent(eventFull))
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
login(LOGIN_METHOD_VULCAN_API) {
|
||||
val list = data.app.db.eventDao().getAllNow(data.profileId).filter { !it.addedManually }
|
||||
VulcanApiAttachments(data, list, eventFull, EventFull::class) { _ ->
|
||||
@ -194,7 +227,6 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
override fun cancel() {
|
||||
d(TAG, "Cancelled")
|
||||
data.cancel()
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
|
@ -19,34 +19,68 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070
|
||||
const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080
|
||||
const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090
|
||||
const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100
|
||||
|
||||
const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
|
||||
|
||||
const val ENDPOINT_VULCAN_HEBE_MAIN = 3000
|
||||
const val ENDPOINT_VULCAN_HEBE_PUSH_CONFIG = 3005
|
||||
const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010
|
||||
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
|
||||
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
|
||||
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
|
||||
const val ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY = 3050
|
||||
const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060
|
||||
const val ENDPOINT_VULCAN_HEBE_NOTICES = 3070
|
||||
const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080
|
||||
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090
|
||||
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100
|
||||
const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
|
||||
|
||||
val VulcanFeatures = listOf(
|
||||
// timetable
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
|
||||
ENDPOINT_VULCAN_API_TIMETABLE to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_TIMETABLE to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// agenda
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf(
|
||||
ENDPOINT_VULCAN_API_EVENTS to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_EXAMS to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// grades
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf(
|
||||
ENDPOINT_VULCAN_API_GRADES to LOGIN_METHOD_VULCAN_API,
|
||||
ENDPOINT_VULCAN_API_GRADES_SUMMARY to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_GRADES to LOGIN_METHOD_VULCAN_HEBE,
|
||||
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// homework
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf(
|
||||
ENDPOINT_VULCAN_API_HOMEWORK to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_HOMEWORK to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// behaviour
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
|
||||
ENDPOINT_VULCAN_API_NOTICES to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_NOTICES to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// attendance
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf(
|
||||
ENDPOINT_VULCAN_API_ATTENDANCE to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_ATTENDANCE to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
// messages
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_INBOX, listOf(
|
||||
ENDPOINT_VULCAN_API_MESSAGES_INBOX to LOGIN_METHOD_VULCAN_API
|
||||
@ -54,6 +88,12 @@ val VulcanFeatures = listOf(
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_SENT, listOf(
|
||||
ENDPOINT_VULCAN_API_MESSAGES_SENT to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_INBOX, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_SENT, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
|
||||
|
||||
// push config
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
|
||||
@ -61,18 +101,37 @@ val VulcanFeatures = listOf(
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)).withShouldSync { data ->
|
||||
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
|
||||
},
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE)).withShouldSync { data ->
|
||||
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
|
||||
},
|
||||
|
||||
/**
|
||||
* Lucky number - using WEB Main.
|
||||
*/
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
|
||||
ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() },
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN))
|
||||
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
|
||||
.withPriority(2),
|
||||
/**
|
||||
* Lucky number - using Hebe API
|
||||
*/
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE))
|
||||
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
|
||||
.withPriority(1),
|
||||
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API,
|
||||
ENDPOINT_VULCAN_API_DICTIONARIES to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API))
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_MAIN to LOGIN_METHOD_VULCAN_HEBE,
|
||||
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE))
|
||||
/*Feature(LOGIN_TYPE_VULCAN, FEATURE_STUDENT_INFO, listOf(
|
||||
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB)),
|
||||
|
@ -5,9 +5,13 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
|
||||
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_VULCAN_API
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
@ -15,9 +19,40 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
private const val TAG = "VulcanData"
|
||||
}
|
||||
|
||||
init {
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
private var firstSemesterSync = false
|
||||
private val firstSemesterSyncExclude = listOf(
|
||||
ENDPOINT_VULCAN_HEBE_MAIN,
|
||||
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
|
||||
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
|
||||
ENDPOINT_VULCAN_HEBE_TIMETABLE,
|
||||
ENDPOINT_VULCAN_HEBE_EXAMS,
|
||||
ENDPOINT_VULCAN_HEBE_HOMEWORK,
|
||||
ENDPOINT_VULCAN_HEBE_NOTICES,
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
|
||||
)
|
||||
|
||||
init { run {
|
||||
if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) {
|
||||
data.error(TAG, ERROR_VULCAN_API_DEPRECATED)
|
||||
return@run
|
||||
}
|
||||
if (data.studentSemesterNumber == 2 && data.profile?.empty != false) {
|
||||
firstSemesterSync = true
|
||||
// set to sync 1st semester first
|
||||
data.studentSemesterId = data.semester1Id
|
||||
data.studentSemesterNumber = 1
|
||||
}
|
||||
nextEndpoint {
|
||||
if (firstSemesterSync) {
|
||||
// at the end, set back 2nd semester
|
||||
data.studentSemesterId = data.semester2Id
|
||||
data.studentSemesterNumber = 2
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
}}
|
||||
|
||||
private fun nextEndpoint(onSuccess: () -> Unit) {
|
||||
if (data.targetEndpointIds.isEmpty()) {
|
||||
@ -30,7 +65,21 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
useEndpoint(id, lastSync) {
|
||||
if (firstSemesterSync && id !in firstSemesterSyncExclude) {
|
||||
// sync 2nd semester after every endpoint
|
||||
data.studentSemesterId = data.semester2Id
|
||||
data.studentSemesterNumber = 2
|
||||
useEndpoint(id, lastSync) {
|
||||
// set 1st semester back for the next endpoint
|
||||
data.studentSemesterId = data.semester1Id
|
||||
data.studentSemesterNumber = 1
|
||||
// progress further
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
return@useEndpoint
|
||||
}
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
@ -91,6 +140,67 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||
VulcanWebLuckyNumber(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_MAIN -> {
|
||||
if (data.profile == null) {
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_MAIN)
|
||||
return
|
||||
}
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
|
||||
VulcanHebeMain(data, lastSync).getStudents(
|
||||
profile = data.profile,
|
||||
profileList = null
|
||||
) {
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_MAIN)
|
||||
}
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
|
||||
VulcanHebePushConfig(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_teachers)
|
||||
VulcanHebeAddressbook(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_TIMETABLE -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||
VulcanHebeTimetable(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_EXAMS -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_exams)
|
||||
VulcanHebeExams(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_GRADES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||
VulcanHebeGrades(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
|
||||
VulcanHebeGradeSummary(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_HOMEWORK -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_homework)
|
||||
VulcanHebeHomework(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_NOTICES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_notices)
|
||||
VulcanHebeNotices(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_ATTENDANCE -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
|
||||
VulcanHebeAttendance(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
|
||||
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
|
||||
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_SENT)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||
VulcanHebeLuckyNumber(data, lastSync, onSuccess)
|
||||
}
|
||||
else -> onSuccess(endpointId)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
|
||||
|
||||
import android.os.Build
|
||||
import androidx.core.util.set
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import io.github.wulkanowy.signer.hebe.getSignatureHeaders
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.HebeFilterType
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LessonRange
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Subject
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URLEncoder
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebe"
|
||||
}
|
||||
|
||||
val profileId
|
||||
get() = data.profile?.id ?: -1
|
||||
|
||||
val profile
|
||||
get() = data.profile
|
||||
|
||||
fun getDateTime(
|
||||
json: JsonObject?,
|
||||
key: String,
|
||||
default: Long = System.currentTimeMillis()
|
||||
): Long {
|
||||
val date = json.getJsonObject(key)
|
||||
return date.getLong("Timestamp") ?: return default
|
||||
}
|
||||
|
||||
fun getDate(json: JsonObject?, key: String): Date? {
|
||||
val date = json.getJsonObject(key)
|
||||
return date.getString("Date")?.let { Date.fromY_m_d(it) }
|
||||
}
|
||||
|
||||
fun getTeacherId(json: JsonObject?, key: String): Long? {
|
||||
val teacher = json.getJsonObject(key)
|
||||
val teacherId = teacher.getLong("Id") ?: return null
|
||||
if (data.teacherList[teacherId] == null) {
|
||||
data.teacherList[teacherId] = Teacher(
|
||||
data.profileId,
|
||||
teacherId,
|
||||
teacher.getString("Name") ?: "",
|
||||
teacher.getString("Surname") ?: ""
|
||||
)
|
||||
}
|
||||
return teacherId
|
||||
}
|
||||
|
||||
fun getSubjectId(json: JsonObject?, key: String): Long? {
|
||||
val subject = json.getJsonObject(key)
|
||||
val subjectId = subject.getLong("Id") ?: return null
|
||||
if (data.subjectList[subjectId] == null) {
|
||||
data.subjectList[subjectId] = Subject(
|
||||
data.profileId,
|
||||
subjectId,
|
||||
subject.getString("Name") ?: "",
|
||||
subject.getString("Kod") ?: ""
|
||||
)
|
||||
}
|
||||
return subjectId
|
||||
}
|
||||
|
||||
fun getTeamId(json: JsonObject?, key: String): Long? {
|
||||
val team = json.getJsonObject(key)
|
||||
val teamId = team.getLong("Id") ?: return null
|
||||
if (data.teamList[teamId] == null) {
|
||||
var name = team.getString("Shortcut")
|
||||
?: team.getString("Name")
|
||||
?: ""
|
||||
name = "${profile?.studentClassName ?: ""} $name"
|
||||
data.teamList[teamId] = Team(
|
||||
data.profileId,
|
||||
teamId,
|
||||
name,
|
||||
Team.TYPE_VIRTUAL,
|
||||
"${data.schoolCode}:$name",
|
||||
-1
|
||||
)
|
||||
}
|
||||
return teamId
|
||||
}
|
||||
|
||||
fun getClassId(json: JsonObject?, key: String): Long? {
|
||||
val team = json.getJsonObject(key)
|
||||
val teamId = team.getLong("Id") ?: return null
|
||||
if (data.teamList[teamId] == null) {
|
||||
val name = data.profile?.studentClassName
|
||||
?: team.getString("Name")
|
||||
?: ""
|
||||
data.teamList[teamId] = Team(
|
||||
data.profileId,
|
||||
teamId,
|
||||
name,
|
||||
Team.TYPE_CLASS,
|
||||
"${data.schoolCode}:$name",
|
||||
-1
|
||||
)
|
||||
}
|
||||
return teamId
|
||||
}
|
||||
|
||||
fun getLessonRange(json: JsonObject?, key: String): LessonRange? {
|
||||
val timeslot = json.getJsonObject(key)
|
||||
val position = timeslot.getInt("Position") ?: return null
|
||||
val start = timeslot.getString("Start") ?: return null
|
||||
val end = timeslot.getString("End") ?: return null
|
||||
val lessonRange = LessonRange(
|
||||
data.profileId,
|
||||
position,
|
||||
Time.fromH_m(start),
|
||||
Time.fromH_m(end)
|
||||
)
|
||||
data.lessonRanges[position] = lessonRange
|
||||
return lessonRange
|
||||
}
|
||||
|
||||
fun getSemester(json: JsonObject?): Int {
|
||||
val periodId = json.getInt("PeriodId") ?: return 1
|
||||
return if (periodId == data.semester1Id)
|
||||
1
|
||||
else
|
||||
2
|
||||
}
|
||||
|
||||
fun isCurrentYear(date: Date): Boolean {
|
||||
return profile?.let { profile ->
|
||||
return@let date >= profile.dateSemester1Start
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun isCurrentYear(dateTime: Long): Boolean {
|
||||
return profile?.let { profile ->
|
||||
return@let dateTime >= profile.dateSemester1Start.inMillis
|
||||
} ?: false
|
||||
}
|
||||
|
||||
inline fun <reified T> apiRequest(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
method: Int = GET,
|
||||
payload: JsonElement? = null,
|
||||
baseUrl: Boolean = false,
|
||||
firebaseToken: String? = null,
|
||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||
) {
|
||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
||||
|
||||
d(tag, "Request: Vulcan/Hebe - $url")
|
||||
|
||||
val privateKey = data.hebePrivateKey
|
||||
val publicHash = data.hebePublicHash
|
||||
|
||||
if (privateKey == null || publicHash == null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
||||
return
|
||||
}
|
||||
|
||||
val timestamp = ZonedDateTime.now(ZoneId.of("GMT"))
|
||||
val timestampMillis = timestamp.toInstant().toEpochMilli()
|
||||
val timestampIso = timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"))
|
||||
|
||||
val finalPayload = if (payload != null) {
|
||||
JsonObject(
|
||||
"AppName" to VULCAN_HEBE_APP_NAME,
|
||||
"AppVersion" to VULCAN_HEBE_APP_VERSION,
|
||||
"CertificateId" to publicHash,
|
||||
"Envelope" to payload,
|
||||
"FirebaseToken" to (firebaseToken ?: data.app.config.sync.tokenVulcanHebe),
|
||||
"API" to 1,
|
||||
"RequestId" to UUID.randomUUID().toString(),
|
||||
"Timestamp" to timestampMillis,
|
||||
"TimestampFormatted" to timestampIso
|
||||
)
|
||||
} else null
|
||||
val jsonString = finalPayload?.toString()
|
||||
|
||||
val headers = getSignatureHeaders(
|
||||
publicHash,
|
||||
privateKey,
|
||||
jsonString,
|
||||
endpoint,
|
||||
timestamp
|
||||
)
|
||||
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
if (json == null) {
|
||||
data.error(
|
||||
ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val status = json.getJsonObject("Status")
|
||||
if (status?.getInt("Code") != 0) {
|
||||
data.error(
|
||||
ApiError(tag, ERROR_VULCAN_HEBE_OTHER)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json.toString())
|
||||
)
|
||||
}
|
||||
|
||||
val envelope = if (json.get("Envelope").isJsonNull && null is T)
|
||||
null as T
|
||||
else when (T::class.java) {
|
||||
JsonObject::class.java -> json.getJsonObject("Envelope") as T
|
||||
JsonArray::class.java -> json.getJsonArray("Envelope") as T
|
||||
java.lang.Boolean::class.java -> json.getBoolean("Envelope") as T
|
||||
else -> {
|
||||
data.error(
|
||||
ApiError(tag, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
onSuccess(envelope, response)
|
||||
} catch (e: Exception) {
|
||||
data.error(
|
||||
ApiError(tag, EXCEPTION_VULCAN_HEBE_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(url)
|
||||
.userAgent(VULCAN_HEBE_USER_AGENT)
|
||||
.addHeader("vOS", "Android")
|
||||
.addHeader("vDeviceModel", Build.MODEL)
|
||||
.addHeader("vAPI", "1")
|
||||
.apply {
|
||||
if (data.hebeContext != null)
|
||||
addHeader("vContext", data.hebeContext)
|
||||
headers.forEach {
|
||||
addHeader(it.key, it.value)
|
||||
}
|
||||
when (method) {
|
||||
GET -> get()
|
||||
POST -> {
|
||||
post()
|
||||
setTextBody(jsonString, MediaTypeUtils.APPLICATION_JSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
inline fun <reified T> apiGet(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
query: Map<String, String> = mapOf(),
|
||||
baseUrl: Boolean = false,
|
||||
firebaseToken: String? = null,
|
||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||
) {
|
||||
val queryPath = query.map {
|
||||
it.key + "=" + URLEncoder.encode(it.value, "UTF-8").replace("+", "%20")
|
||||
}.join("&")
|
||||
apiRequest(
|
||||
tag,
|
||||
if (query.isNotEmpty()) "$endpoint?$queryPath" else endpoint,
|
||||
baseUrl = baseUrl,
|
||||
firebaseToken = firebaseToken,
|
||||
onSuccess = onSuccess
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified T> apiPost(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
payload: JsonElement,
|
||||
baseUrl: Boolean = false,
|
||||
firebaseToken: String? = null,
|
||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||
) {
|
||||
apiRequest(
|
||||
tag,
|
||||
endpoint,
|
||||
method = POST,
|
||||
payload,
|
||||
baseUrl = baseUrl,
|
||||
firebaseToken = firebaseToken,
|
||||
onSuccess = onSuccess
|
||||
)
|
||||
}
|
||||
|
||||
fun apiGetList(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
filterType: HebeFilterType? = null,
|
||||
dateFrom: Date? = null,
|
||||
dateTo: Date? = null,
|
||||
lastSync: Long? = null,
|
||||
folder: Int? = null,
|
||||
params: Map<String, String> = mapOf(),
|
||||
includeFilterType: Boolean = true,
|
||||
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
|
||||
) {
|
||||
val url = if (includeFilterType && filterType != null)
|
||||
"$endpoint/${filterType.endpoint}"
|
||||
else endpoint
|
||||
|
||||
val query = params.toMutableMap()
|
||||
|
||||
when (filterType) {
|
||||
HebeFilterType.BY_PUPIL -> {
|
||||
query["unitId"] = data.studentUnitId.toString()
|
||||
query["pupilId"] = data.studentId.toString()
|
||||
query["periodId"] = data.studentSemesterId.toString()
|
||||
}
|
||||
HebeFilterType.BY_PERSON -> {
|
||||
query["loginId"] = data.studentLoginId.toString()
|
||||
}
|
||||
HebeFilterType.BY_PERIOD -> {
|
||||
query["periodId"] = data.studentSemesterId.toString()
|
||||
query["pupilId"] = data.studentId.toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (dateFrom != null)
|
||||
query["dateFrom"] = dateFrom.stringY_m_d
|
||||
if (dateTo != null)
|
||||
query["dateTo"] = dateTo.stringY_m_d
|
||||
|
||||
if (folder != null)
|
||||
query["folder"] = folder.toString()
|
||||
|
||||
val semester1Start = profile?.dateSemester1Start?.inMillis
|
||||
|
||||
query["lastId"] = "-2147483648" // don't ask, it's just Vulcan
|
||||
query["pageSize"] = "500"
|
||||
query["lastSyncDate"] = LocalDateTime
|
||||
.ofInstant(
|
||||
Instant.ofEpochMilli(lastSync ?: semester1Start ?: 0),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
apiGet(tag, url, query) { json: JsonArray, response ->
|
||||
onSuccess(json.map { it.asJsonObject }, response)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,12 +11,11 @@ import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.droidsonroids.jspoon.Jspoon
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorBlank
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import java.io.File
|
||||
@ -83,10 +82,12 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
onResult(symbol, STATE_NO_REGISTER)
|
||||
return
|
||||
}
|
||||
if (!validateCallback(text, response, jsonResponse = false)) {
|
||||
if (!validateCallback(symbol, text, response, jsonResponse = false)) {
|
||||
return
|
||||
}
|
||||
data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L
|
||||
data.webExpiryTime = data.webExpiryTime.toMutableMap().also { map ->
|
||||
map[symbol] = (Date.fromIso(certificate.expiryDate) / 1000L).toString()
|
||||
}
|
||||
onResult(symbol, STATE_SUCCESS)
|
||||
}
|
||||
|
||||
@ -120,7 +121,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List<String>) -> Unit) {
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
if (!validateCallback(text, response, jsonResponse = false) || text == null) {
|
||||
if (!validateCallback(symbol, text, response, jsonResponse = false) || text == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -136,7 +137,30 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
}
|
||||
}
|
||||
|
||||
data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||
data.webPermissions = data.webPermissions.toMutableMap().also { map ->
|
||||
val permissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||
if (permissions?.isNotBlank() == true) {
|
||||
val studentId = permissions.split("|")
|
||||
.getOrNull(0)
|
||||
?.base64DecodeToString()
|
||||
?.toJsonObject()
|
||||
?.getJsonArray("AuthInfos")
|
||||
?.asJsonObjectList()
|
||||
?.flatMap { authInfo ->
|
||||
authInfo.getJsonArray("UczenIds")
|
||||
?.map { it.asInt }
|
||||
?: listOf()
|
||||
}
|
||||
?.firstOrNull()
|
||||
?.toString()
|
||||
data.app.cookieJar.set(
|
||||
data.webHost ?: "vulcan.net.pl",
|
||||
"idBiezacyUczen",
|
||||
studentId
|
||||
)
|
||||
}
|
||||
map[symbol] = permissions
|
||||
}
|
||||
|
||||
val schoolSymbols = mutableListOf<String>()
|
||||
val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/"
|
||||
@ -144,7 +168,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
var count = 0
|
||||
while (clientIndex != -1 && count < 100) {
|
||||
val startIndex = clientIndex + clientUrl.length
|
||||
val endIndex = text.indexOf('/', startIndex = startIndex)
|
||||
val endIndex = text.indexOf('"', startIndex = startIndex)
|
||||
val schoolSymbol = text.substring(startIndex, endIndex)
|
||||
schoolSymbols += schoolSymbol
|
||||
clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
|
||||
@ -186,7 +210,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
|
||||
private fun validateCallback(symbol: String, text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
|
||||
if (text == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
@ -207,11 +231,13 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
|
||||
val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl")
|
||||
val authCookie = cookies["EfebSsoAuthCookie"]
|
||||
if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) {
|
||||
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie)
|
||||
if ((authCookie == null || authCookie == "null") && data.webAuthCookie[symbol] != null) {
|
||||
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie[symbol])
|
||||
}
|
||||
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) {
|
||||
data.webAuthCookie = authCookie
|
||||
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie[symbol]) {
|
||||
data.webAuthCookie = data.webAuthCookie.toMutableMap().also { map ->
|
||||
map[symbol] = authCookie
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -250,7 +276,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
if (!validateCallback(text, response))
|
||||
if (!validateCallback(data.symbol ?: "default", text, response))
|
||||
return
|
||||
|
||||
try {
|
||||
|
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
enum class HebeFilterType(val endpoint: String) {
|
||||
BY_PUPIL("byPupil"),
|
||||
BY_PERSON("byPerson"),
|
||||
BY_PERIOD("byPeriod")
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import androidx.core.util.set
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ADDRESSBOOK
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_ADDRESSBOOK
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_EDUCATOR
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_OTHER
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENTS_COUNCIL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
|
||||
import kotlin.text.replace
|
||||
|
||||
class VulcanHebeAddressbook(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeAddressbook"
|
||||
}
|
||||
|
||||
private fun String.removeUnitName(unitName: String?): String {
|
||||
return (unitName ?: data.schoolShort)?.let {
|
||||
this.replace("($it)", "").trim()
|
||||
} ?: this
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_ADDRESSBOOK,
|
||||
HebeFilterType.BY_PERSON,
|
||||
lastSync = lastSync,
|
||||
includeFilterType = false
|
||||
) { list, _ ->
|
||||
list.forEach { person ->
|
||||
val id = person.getString("Id") ?: return@forEach
|
||||
val loginId = person.getString("LoginId") ?: return@forEach
|
||||
|
||||
val idType = id.split("-")
|
||||
.getOrNull(0)
|
||||
val idLong = id.split("-")
|
||||
.getOrNull(1)
|
||||
?.toLongOrNull()
|
||||
?: return@forEach
|
||||
|
||||
val typeBase = when (idType) {
|
||||
"e" -> TYPE_TEACHER
|
||||
"c" -> TYPE_PARENT
|
||||
"p" -> TYPE_STUDENT
|
||||
else -> TYPE_OTHER
|
||||
}
|
||||
|
||||
val name = person.getString("Name") ?: ""
|
||||
val surname = person.getString("Surname") ?: ""
|
||||
val namePrefix = "$surname $name - "
|
||||
|
||||
val teacher = data.teacherList[idLong] ?: Teacher(
|
||||
data.profileId,
|
||||
idLong,
|
||||
name,
|
||||
surname,
|
||||
loginId
|
||||
).also {
|
||||
data.teacherList[idLong] = it
|
||||
}
|
||||
|
||||
person.getJsonArray("Roles")?.asJsonObjectList()?.onEach { role ->
|
||||
var roleText: String? = null
|
||||
val unitName = role.getString("ConstituentUnitSymbol")
|
||||
|
||||
val personType = when (role.getInt("RoleOrder")) {
|
||||
0 -> { /* Wychowawca */
|
||||
roleText = role.getString("ClassSymbol")
|
||||
?.removeUnitName(unitName)
|
||||
TYPE_EDUCATOR
|
||||
}
|
||||
1 -> TYPE_TEACHER /* Nauczyciel */
|
||||
2 -> return@onEach /* Pracownik */
|
||||
3 -> { /* Rada rodziców */
|
||||
roleText = role.getString("Address")
|
||||
?.removeUnitName(unitName)
|
||||
?.removePrefix(namePrefix)
|
||||
?.trim()
|
||||
TYPE_PARENTS_COUNCIL
|
||||
}
|
||||
5 -> {
|
||||
roleText = role.getString("RoleName")
|
||||
?.plus(" - ")
|
||||
?.plus(
|
||||
role.getString("Address")
|
||||
?.removeUnitName(unitName)
|
||||
?.removePrefix(namePrefix)
|
||||
?.trim()
|
||||
)
|
||||
TYPE_STUDENT
|
||||
}
|
||||
else -> TYPE_OTHER
|
||||
}
|
||||
|
||||
teacher.setTeacherType(personType)
|
||||
teacher.typeDescription = roleText
|
||||
}
|
||||
|
||||
if (teacher.type == 0)
|
||||
teacher.setTeacherType(typeBase)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2021-2-21
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ATTENDANCE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_ATTENDANCE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebeAttendance(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeAttendance"
|
||||
}
|
||||
|
||||
init {
|
||||
val semesterNumber = data.studentSemesterNumber
|
||||
val startDate = profile?.getSemesterStart(semesterNumber)
|
||||
val endDate = profile?.getSemesterEnd(semesterNumber)
|
||||
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_ATTENDANCE,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
dateFrom = startDate,
|
||||
dateTo = endDate,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { attendance ->
|
||||
val id = attendance.getLong("AuxPresenceId") ?: return@forEach
|
||||
val type = attendance.getJsonObject("PresenceType") ?: return@forEach
|
||||
val baseType = getBaseType(type)
|
||||
val typeName = type.getString("Name") ?: return@forEach
|
||||
val typeCategoryId = type.getLong("CategoryId") ?: return@forEach
|
||||
val typeSymbol = type.getString("Symbol") ?: return@forEach
|
||||
val typeShort = when (typeCategoryId.toInt()) {
|
||||
6, 8 -> typeSymbol
|
||||
else -> data.app.attendanceManager.getTypeShort(baseType)
|
||||
}
|
||||
val typeColor = when (typeCategoryId.toInt()) {
|
||||
1 -> 0xffffffff // obecność
|
||||
2 -> 0xffffa687 // nieobecność
|
||||
3 -> 0xfffcc150 // nieobecność usprawiedliwiona
|
||||
4 -> 0xffede049 // spóźnienie
|
||||
5 -> 0xffbbdd5f // spóźnienie usprawiedliwione
|
||||
6 -> 0xffa9c9fd // nieobecny z przyczyn szkolnych
|
||||
7 -> 0xffddbbe5 // zwolniony
|
||||
8 -> 0xffffffff // usunięty wpis
|
||||
else -> null
|
||||
}?.toInt()
|
||||
val date = getDate(attendance, "Day") ?: return@forEach
|
||||
val lessonRange = getLessonRange(attendance, "TimeSlot")
|
||||
val startTime = lessonRange?.startTime
|
||||
val semester = profile?.dateToSemester(date) ?: return@forEach
|
||||
val teacherId = attendance.getJsonObject("TeacherPrimary")?.getLong("Id") ?: -1
|
||||
val subjectId = attendance.getJsonObject("Subject")?.getLong("Id") ?: -1
|
||||
val addedDate = getDateTime(attendance, "DateModify")
|
||||
val lessonNumber = lessonRange?.lessonNumber
|
||||
val isCounted = attendance.getBoolean("CalculatePresence")
|
||||
?: (baseType != Attendance.TYPE_RELEASED)
|
||||
|
||||
val attendanceObject = Attendance(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
baseType = baseType,
|
||||
typeName = typeName,
|
||||
typeShort = typeShort,
|
||||
typeSymbol = typeSymbol,
|
||||
typeColor = typeColor,
|
||||
date = date,
|
||||
startTime = startTime,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId,
|
||||
addedDate = addedDate
|
||||
).also {
|
||||
it.lessonTopic = attendance.getString("Topic")
|
||||
it.lessonNumber = lessonNumber
|
||||
it.isCounted = isCounted
|
||||
}
|
||||
|
||||
data.attendanceList.add(attendanceObject)
|
||||
if (baseType != Attendance.TYPE_PRESENT) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_ATTENDANCE,
|
||||
attendanceObject.id,
|
||||
profile?.empty ?: true
|
||||
|| baseType == Attendance.TYPE_PRESENT_CUSTOM
|
||||
|| baseType == Attendance.TYPE_UNKNOWN,
|
||||
profile?.empty ?: true
|
||||
|| baseType == Attendance.TYPE_PRESENT_CUSTOM
|
||||
|| baseType == Attendance.TYPE_UNKNOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ATTENDANCE, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_ATTENDANCE)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBaseType(attendanceType: JsonObject): Int {
|
||||
val absent = attendanceType.getBoolean("Absence") ?: false
|
||||
val excused = attendanceType.getBoolean("AbsenceJustified") ?: false
|
||||
return if (absent) {
|
||||
if (excused)
|
||||
Attendance.TYPE_ABSENT_EXCUSED
|
||||
else
|
||||
Attendance.TYPE_ABSENT
|
||||
} else {
|
||||
val belated = attendanceType.getBoolean("Late") ?: false
|
||||
val released = attendanceType.getBoolean("LegalAbsence") ?: false
|
||||
val present = attendanceType.getBoolean("Presence") ?: true
|
||||
if (belated)
|
||||
if (excused)
|
||||
Attendance.TYPE_BELATED_EXCUSED
|
||||
else
|
||||
Attendance.TYPE_BELATED
|
||||
else if (released)
|
||||
Attendance.TYPE_RELEASED
|
||||
else if (present)
|
||||
if (attendanceType.getInt("CategoryId") != 1)
|
||||
Attendance.TYPE_PRESENT_CUSTOM
|
||||
else
|
||||
Attendance.TYPE_PRESENT
|
||||
else
|
||||
Attendance.TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_EXAMS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_EXAMS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebeExams(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeExams"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_EXAMS,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { exam ->
|
||||
val id = exam.getLong("Id") ?: return@forEach
|
||||
val eventDate = getDate(exam, "Deadline") ?: return@forEach
|
||||
val subjectId = getSubjectId(exam, "Subject") ?: -1
|
||||
val teacherId = getTeacherId(exam, "Creator") ?: -1
|
||||
val teamId = getTeamId(exam, "Distribution")
|
||||
?: getClassId(exam, "Class")
|
||||
?: data.teamClass?.id
|
||||
?: -1
|
||||
val topic = exam.getString("Content")?.trim() ?: ""
|
||||
|
||||
if (!isCurrentYear(eventDate)) return@forEach
|
||||
|
||||
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
|
||||
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
val type = when (exam.getString("Type")) {
|
||||
"Praca klasowa",
|
||||
"Sprawdzian" -> Event.TYPE_EXAM
|
||||
"Kartkówka" -> Event.TYPE_SHORT_QUIZ
|
||||
else -> Event.TYPE_DEFAULT
|
||||
}
|
||||
|
||||
val eventObject = Event(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
date = eventDate,
|
||||
time = startTime,
|
||||
topic = topic,
|
||||
color = null,
|
||||
type = type,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId,
|
||||
teamId = teamId
|
||||
)
|
||||
|
||||
data.eventList.add(eventObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_EVENT,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_EXAMS, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_EXAMS)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.DAY
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanHebeGradeSummary(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeGradeSummary"
|
||||
}
|
||||
|
||||
init {
|
||||
val entries = mapOf(
|
||||
"Entry_1" to
|
||||
if (data.studentSemesterNumber == 1)
|
||||
Grade.TYPE_SEMESTER1_PROPOSED
|
||||
else Grade.TYPE_SEMESTER2_PROPOSED,
|
||||
"Entry_2" to
|
||||
if (data.studentSemesterNumber == 1)
|
||||
Grade.TYPE_SEMESTER1_FINAL
|
||||
else Grade.TYPE_SEMESTER2_FINAL
|
||||
)
|
||||
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { grade ->
|
||||
val subjectId = getSubjectId(grade, "Subject") ?: return@forEach
|
||||
val addedDate = getDateTime(grade, "DateModify")
|
||||
|
||||
entries.onEach { (key, type) ->
|
||||
val id = subjectId * -100 - type
|
||||
val entry = grade.getString(key) ?: return@onEach
|
||||
val value = Utils.getGradeValue(entry)
|
||||
val color = Utils.getVulcanGradeColor(entry)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = entry,
|
||||
type = type,
|
||||
value = value,
|
||||
weight = 0f,
|
||||
color = color,
|
||||
category = "",
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = data.studentSemesterNumber,
|
||||
teacherId = -1,
|
||||
subjectId = subjectId,
|
||||
addedDate = addedDate
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_GRADE,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY, 1 * DAY)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class VulcanHebeGrades(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeGrades"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_GRADES,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { grade ->
|
||||
val id = grade.getLong("Id") ?: return@forEach
|
||||
|
||||
val column = grade.getJsonObject("Column")
|
||||
val category = column.getJsonObject("Category")
|
||||
val categoryText = category.getString("Name")
|
||||
|
||||
val teacherId = getTeacherId(grade, "Creator") ?: -1
|
||||
val subjectId = getSubjectId(column, "Subject") ?: -1
|
||||
|
||||
val description = column.getString("Name")
|
||||
val comment = grade.getString("Comment")
|
||||
var value = grade.getFloat("Value")
|
||||
var weight = column.getFloat("Weight") ?: 0.0f
|
||||
val numerator = grade.getFloat("Numerator ")
|
||||
val denominator = grade.getFloat("Denominator")
|
||||
val addedDate = getDateTime(grade, "DateModify")
|
||||
|
||||
var finalDescription = ""
|
||||
|
||||
var name = when (numerator != null && denominator != null) {
|
||||
true -> {
|
||||
value = numerator / denominator
|
||||
finalDescription += DecimalFormat("#.##").format(numerator) +
|
||||
"/" + DecimalFormat("#.##").format(denominator)
|
||||
weight = 0.0f
|
||||
(value * 100).roundToInt().toString() + "%"
|
||||
}
|
||||
else -> {
|
||||
if (value == null) weight = 0.0f
|
||||
|
||||
grade.getString("Content") ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
comment?.also {
|
||||
if (name == "") name = it
|
||||
else finalDescription = (if (finalDescription == "") "" else " ") + it
|
||||
}
|
||||
|
||||
description?.also {
|
||||
finalDescription = (if (finalDescription == "") "" else " - ") + it
|
||||
}
|
||||
|
||||
val columnColor = column.getInt("Color") ?: 0
|
||||
val color = if (columnColor == 0)
|
||||
when (name) {
|
||||
"1-", "1", "1+" -> 0xffd65757
|
||||
"2-", "2", "2+" -> 0xff9071b3
|
||||
"3-", "3", "3+" -> 0xffd2ab24
|
||||
"4-", "4", "4+" -> 0xff50b6d6
|
||||
"5-", "5", "5+" -> 0xff2cbd92
|
||||
"6-", "6", "6+" -> 0xff91b43c
|
||||
else -> 0xff3D5F9C
|
||||
}.toInt()
|
||||
else
|
||||
columnColor
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = Grade.TYPE_NORMAL,
|
||||
value = value ?: 0.0f,
|
||||
weight = weight,
|
||||
color = color,
|
||||
category = categoryText,
|
||||
description = finalDescription,
|
||||
comment = null,
|
||||
semester = getSemester(column),
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId,
|
||||
addedDate = addedDate
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_GRADE,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_GRADES, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_GRADES)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.asJsonObjectList
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getJsonArray
|
||||
import pl.szczodrzynski.edziennik.getLong
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanHebeHomework(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeHomework"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_HOMEWORK,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { exam ->
|
||||
val id = exam.getLong("IdHomework") ?: return@forEach
|
||||
val eventDate = getDate(exam, "Deadline") ?: return@forEach
|
||||
val subjectId = getSubjectId(exam, "Subject") ?: -1
|
||||
val teacherId = getTeacherId(exam, "Creator") ?: -1
|
||||
val teamId = data.teamClass?.id ?: -1
|
||||
val topic = exam.getString("Content")?.trim() ?: ""
|
||||
|
||||
if (!isCurrentYear(eventDate)) return@forEach
|
||||
|
||||
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
|
||||
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
val eventObject = Event(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
date = eventDate,
|
||||
time = startTime,
|
||||
topic = topic,
|
||||
color = null,
|
||||
type = Event.TYPE_HOMEWORK,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId,
|
||||
teamId = teamId
|
||||
)
|
||||
|
||||
val attachments = exam.getJsonArray("Attachments")
|
||||
?.asJsonObjectList()
|
||||
?: return@forEach
|
||||
|
||||
for (attachment in attachments) {
|
||||
val fileName = attachment.getString("Name") ?: continue
|
||||
val url = attachment.getString("Link") ?: continue
|
||||
val attachmentName = "$fileName:$url"
|
||||
val attachmentId = Utils.crc32(attachmentName.toByteArray())
|
||||
|
||||
eventObject.addAttachment(
|
||||
id = attachmentId,
|
||||
name = attachmentName
|
||||
)
|
||||
}
|
||||
|
||||
data.eventList.add(eventObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_HOMEWORK,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_HOMEWORK, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_HOMEWORK)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class VulcanHebeLuckyNumber(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeLuckyNumber"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGet(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER,
|
||||
query = mapOf(
|
||||
"constituentId" to data.studentConstituentId.toString(),
|
||||
"day" to Date.getToday().stringY_m_d
|
||||
)
|
||||
) { lucky: JsonObject?, _ ->
|
||||
// sync tomorrow if lucky number set or is weekend or afternoon
|
||||
var nextSync = Date.getToday().stepForward(0, 0, 1).inMillis
|
||||
if (lucky == null) {
|
||||
if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 12) {
|
||||
// working days morning, sync always
|
||||
nextSync = SYNC_ALWAYS
|
||||
}
|
||||
}
|
||||
else {
|
||||
val luckyNumberDate = Date.fromY_m_d(lucky.getString("Day")) ?: Date.getToday()
|
||||
val luckyNumber = lucky.getInt("Number") ?: -1
|
||||
val luckyNumberObject = LuckyNumber(
|
||||
profileId = profileId,
|
||||
date = luckyNumberDate,
|
||||
number = luckyNumber
|
||||
)
|
||||
|
||||
data.luckyNumberList.add(luckyNumberObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LUCKY_NUMBER,
|
||||
luckyNumberObject.date.value.toLong(),
|
||||
true,
|
||||
profile?.empty ?: false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER, syncAt = nextSync)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MAIN
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MAIN
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class VulcanHebeMain(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long? = null
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeMain"
|
||||
}
|
||||
|
||||
fun getStudents(
|
||||
profile: Profile?,
|
||||
profileList: MutableList<Profile>?,
|
||||
loginStoreId: Int? = null,
|
||||
firstProfileId: Int? = null,
|
||||
onEmpty: (() -> Unit)? = null,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
if (profile == null && (profileList == null || loginStoreId == null || firstProfileId == null))
|
||||
throw IllegalArgumentException()
|
||||
|
||||
apiGet(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_MAIN,
|
||||
query = mapOf("lastSyncDate" to "null"),
|
||||
baseUrl = profile == null
|
||||
) { students: JsonArray, _ ->
|
||||
if (students.isEmpty()) {
|
||||
if (onEmpty != null)
|
||||
onEmpty()
|
||||
else
|
||||
onSuccess()
|
||||
return@apiGet
|
||||
}
|
||||
|
||||
// safe to assume this will be non-null when creating a profile
|
||||
var profileId = firstProfileId ?: loginStoreId ?: 1
|
||||
|
||||
students.forEach { studentEl ->
|
||||
val student = studentEl.asJsonObject
|
||||
|
||||
val pupil = student.getJsonObject("Pupil")
|
||||
val studentId = pupil.getInt("Id") ?: return@forEach
|
||||
|
||||
// check the student ID in case of not first login
|
||||
if (profile != null && data.studentId != studentId)
|
||||
return@forEach
|
||||
|
||||
val unit = student.getJsonObject("Unit")
|
||||
val constituentUnit = student.getJsonObject("ConstituentUnit")
|
||||
val login = student.getJsonObject("Login")
|
||||
val periods = student.getJsonArray("Periods")?.map {
|
||||
it.asJsonObject
|
||||
} ?: listOf()
|
||||
|
||||
val period = periods.firstOrNull {
|
||||
it.getBoolean("Current", false)
|
||||
} ?: return@forEach
|
||||
|
||||
val periodLevel = period.getInt("Level") ?: return@forEach
|
||||
val semester1 = periods.firstOrNull {
|
||||
it.getInt("Level") == periodLevel && it.getInt("Number") == 1
|
||||
}
|
||||
val semester2 = periods.firstOrNull {
|
||||
it.getInt("Level") == periodLevel && it.getInt("Number") == 2
|
||||
}
|
||||
|
||||
val schoolSymbol = unit.getString("Symbol") ?: return@forEach
|
||||
val schoolShort = constituentUnit.getString("Short") ?: return@forEach
|
||||
val schoolCode = "${data.symbol}_$schoolSymbol"
|
||||
|
||||
val studentUnitId = unit.getInt("Id") ?: return@forEach
|
||||
val studentConstituentId = constituentUnit.getInt("Id") ?: return@forEach
|
||||
val studentLoginId = login.getInt("Id") ?: return@forEach
|
||||
//val studentClassId = student.getInt("IdOddzial") ?: return@forEach
|
||||
val studentClassName = student.getString("ClassDisplay") ?: return@forEach
|
||||
val studentFirstName = pupil.getString("FirstName") ?: ""
|
||||
val studentLastName = pupil.getString("Surname") ?: ""
|
||||
val studentNameLong = "$studentFirstName $studentLastName".fixName()
|
||||
val studentNameShort = "$studentFirstName ${studentLastName[0]}.".fixName()
|
||||
val userLogin = login.getString("Value") ?: ""
|
||||
|
||||
val studentSemesterId = period.getInt("Id") ?: return@forEach
|
||||
val studentSemesterNumber = period.getInt("Number") ?: return@forEach
|
||||
|
||||
val hebeContext = student.getString("Context")
|
||||
|
||||
val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true)
|
||||
val accountName = if (isParent)
|
||||
login.getString("DisplayName")?.fixName()
|
||||
else null
|
||||
|
||||
val dateSemester1Start = semester1
|
||||
?.getJsonObject("Start")
|
||||
?.getString("Date")
|
||||
?.let { Date.fromY_m_d(it) }
|
||||
val dateSemester2Start = semester2
|
||||
?.getJsonObject("Start")
|
||||
?.getString("Date")
|
||||
?.let { Date.fromY_m_d(it) }
|
||||
val dateYearEnd = semester2
|
||||
?.getJsonObject("End")
|
||||
?.getString("Date")
|
||||
?.let { Date.fromY_m_d(it) }
|
||||
|
||||
val newProfile = profile ?: Profile(
|
||||
profileId++,
|
||||
loginStoreId!!,
|
||||
LOGIN_TYPE_VULCAN,
|
||||
studentNameLong,
|
||||
userLogin,
|
||||
studentNameLong,
|
||||
studentNameShort,
|
||||
accountName
|
||||
)
|
||||
|
||||
newProfile.apply {
|
||||
this.studentClassName = studentClassName
|
||||
studentData["symbol"] = data.symbol
|
||||
|
||||
studentData["studentId"] = studentId
|
||||
studentData["studentUnitId"] = studentUnitId
|
||||
studentData["studentConstituentId"] = studentConstituentId
|
||||
studentData["studentLoginId"] = studentLoginId
|
||||
studentData["studentSemesterId"] = studentSemesterId
|
||||
studentData["studentSemesterNumber"] = studentSemesterNumber
|
||||
studentData["semester1Id"] = semester1?.getInt("Id") ?: 0
|
||||
studentData["semester2Id"] = semester2?.getInt("Id") ?: 0
|
||||
studentData["schoolSymbol"] = schoolSymbol
|
||||
studentData["schoolShort"] = schoolShort
|
||||
studentData["schoolName"] = schoolCode
|
||||
studentData["hebeContext"] = hebeContext
|
||||
}
|
||||
dateSemester1Start?.let {
|
||||
newProfile.dateSemester1Start = it
|
||||
newProfile.studentSchoolYearStart = it.year
|
||||
}
|
||||
dateSemester2Start?.let { newProfile.dateSemester2Start = it }
|
||||
dateYearEnd?.let { newProfile.dateYearEnd = it }
|
||||
|
||||
if (profile != null)
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_MAIN, 1 * DAY)
|
||||
|
||||
profileList?.add(newProfile)
|
||||
}
|
||||
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import androidx.core.util.set
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.navlib.crc16
|
||||
import kotlin.text.replace
|
||||
|
||||
class VulcanHebeMessages(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeMessagesInbox"
|
||||
}
|
||||
|
||||
private fun getPersonId(json: JsonObject): Long {
|
||||
val senderLoginId = json.getInt("LoginId") ?: return -1
|
||||
/*if (senderLoginId == data.studentLoginId)
|
||||
return -1*/
|
||||
|
||||
val senderName = json.getString("Address") ?: return -1
|
||||
val senderNameSplit = senderName.splitName()
|
||||
val senderLoginIdStr = senderLoginId.toString()
|
||||
val teacher = data.teacherList.singleOrNull { it.loginId == senderLoginIdStr }
|
||||
?: Teacher(
|
||||
profileId,
|
||||
-1 * crc16(senderName).toLong(),
|
||||
senderNameSplit?.second ?: "",
|
||||
senderNameSplit?.first ?: "",
|
||||
senderLoginIdStr
|
||||
).also {
|
||||
it.setTeacherType(Teacher.TYPE_OTHER)
|
||||
data.teacherList[it.id] = it
|
||||
}
|
||||
return teacher.id
|
||||
}
|
||||
|
||||
fun getMessages(messageType: Int) {
|
||||
val folder = when (messageType) {
|
||||
TYPE_RECEIVED -> 1
|
||||
TYPE_SENT -> 2
|
||||
TYPE_DELETED -> 3
|
||||
else -> 1
|
||||
}
|
||||
val endpointId = when (messageType) {
|
||||
TYPE_RECEIVED -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
|
||||
TYPE_SENT -> ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
|
||||
else -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
|
||||
}
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_MESSAGES,
|
||||
HebeFilterType.BY_PERSON,
|
||||
folder = folder,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { message ->
|
||||
val id = message.getLong("Id") ?: return@forEach
|
||||
val subject = message.getString("Subject") ?: return@forEach
|
||||
val body = message.getString("Content") ?: return@forEach
|
||||
|
||||
val sender = message.getJsonObject("Sender") ?: return@forEach
|
||||
|
||||
val sentDate = getDateTime(message, "DateSent")
|
||||
val readDate = getDateTime(message, "DateRead", default = 0)
|
||||
|
||||
if (!isCurrentYear(sentDate)) return@forEach
|
||||
|
||||
val messageObject = Message(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
type = messageType,
|
||||
subject = subject,
|
||||
body = body.replace("\n", "<br>"),
|
||||
senderId = if (messageType == TYPE_RECEIVED) getPersonId(sender) else null,
|
||||
addedDate = sentDate
|
||||
)
|
||||
|
||||
val receivers = message.getJsonArray("Receiver")
|
||||
?.asJsonObjectList()
|
||||
?: return@forEach
|
||||
val receiverReadDate =
|
||||
if (receivers.size == 1) readDate
|
||||
else -1
|
||||
|
||||
for (receiver in receivers) {
|
||||
val messageRecipientObject = MessageRecipient(
|
||||
profileId,
|
||||
if (messageType == TYPE_SENT) getPersonId(receiver) else -1,
|
||||
-1,
|
||||
receiverReadDate,
|
||||
id
|
||||
)
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
}
|
||||
|
||||
val attachments = message.getJsonArray("Attachments")
|
||||
?.asJsonObjectList()
|
||||
?: return@forEach
|
||||
|
||||
for (attachment in attachments) {
|
||||
val fileName = attachment.getString("Name") ?: continue
|
||||
val url = attachment.getString("Link") ?: continue
|
||||
val attachmentName = "$fileName:$url"
|
||||
val attachmentId = Utils.crc32(attachmentName.toByteArray())
|
||||
|
||||
messageObject.addAttachment(
|
||||
id = attachmentId,
|
||||
name = attachmentName,
|
||||
size = -1
|
||||
)
|
||||
}
|
||||
|
||||
data.messageList.add(messageObject)
|
||||
data.setSeenMetadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_MESSAGE,
|
||||
id,
|
||||
readDate > 0 || messageType == TYPE_SENT,
|
||||
readDate > 0 || messageType == TYPE_SENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(
|
||||
endpointId,
|
||||
if (messageType == TYPE_RECEIVED) SYNC_ALWAYS else 1 * DAY,
|
||||
if (messageType == TYPE_RECEIVED) null else DRAWER_ITEM_MESSAGES
|
||||
)
|
||||
onSuccess(endpointId)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
|
||||
class VulcanHebeMessagesChangeStatus(
|
||||
override val data: DataVulcan,
|
||||
private val messageObject: MessageFull,
|
||||
val onSuccess: () -> Unit
|
||||
) : VulcanHebe(data, null) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeMessagesChangeStatus"
|
||||
}
|
||||
|
||||
init {
|
||||
apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS,
|
||||
payload = JsonObject(
|
||||
"MessageId" to messageObject.id,
|
||||
"LoginId" to data.studentLoginId,
|
||||
"Status" to 1
|
||||
)
|
||||
) { _: Boolean, _ ->
|
||||
|
||||
if (!messageObject.seen) {
|
||||
data.setSeenMetadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_MESSAGE,
|
||||
messageObject.id,
|
||||
true,
|
||||
true
|
||||
)
|
||||
)
|
||||
messageObject.seen = true
|
||||
}
|
||||
|
||||
if (messageObject.type != Message.TYPE_SENT) {
|
||||
val messageRecipientObject = MessageRecipient(
|
||||
profileId,
|
||||
-1,
|
||||
-1,
|
||||
System.currentTimeMillis(),
|
||||
messageObject.id
|
||||
)
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
}
|
||||
|
||||
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2021-2-22
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_NOTICES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_NOTICES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebeNotices(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeNotices"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_NOTICES,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { notice ->
|
||||
val id = notice.getLong("Id") ?: return@forEach
|
||||
val type = when (notice.getBoolean("Positive")) {
|
||||
true -> Notice.TYPE_POSITIVE
|
||||
else -> Notice.TYPE_NEUTRAL
|
||||
}
|
||||
val date = getDate(notice, "DateValid") ?: return@forEach
|
||||
val semester = profile?.dateToSemester(date) ?: return@forEach
|
||||
val text = notice.getString("Content") ?: ""
|
||||
val category = notice.getJsonObject("Category")?.getString("Name")
|
||||
val points = notice.getFloat("Points")
|
||||
val teacherId = getTeacherId(notice, "Creator") ?: -1
|
||||
val addedDate = getDateTime(notice, "DateModify")
|
||||
|
||||
if (!isCurrentYear(date)) return@forEach
|
||||
|
||||
val noticeObject = Notice(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
type = type,
|
||||
semester = semester,
|
||||
text = text,
|
||||
category = category,
|
||||
points = points,
|
||||
teacherId = teacherId,
|
||||
addedDate = addedDate
|
||||
)
|
||||
|
||||
data.noticeList.add(noticeObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_NOTICE,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_NOTICES, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_NOTICES)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonPrimitive
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_PUSH_ALL
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_PUSH_CONFIG
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebePushConfig(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebePushConfig"
|
||||
}
|
||||
|
||||
init {
|
||||
apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_PUSH_ALL,
|
||||
payload = JsonPrimitive("on")
|
||||
) { _: Boolean, _ ->
|
||||
// sync always: this endpoint has .shouldSync set
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG, SYNC_ALWAYS)
|
||||
data.app.config.sync.tokenVulcanList =
|
||||
data.app.config.sync.tokenVulcanList + profileId
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
|
||||
class VulcanHebeSendMessage(
|
||||
override val data: DataVulcan,
|
||||
val recipients: List<Teacher>,
|
||||
val subject: String,
|
||||
val text: String,
|
||||
val onSuccess: () -> Unit
|
||||
) : VulcanHebe(data, null) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeSendMessage"
|
||||
}
|
||||
|
||||
init {
|
||||
val recipientsArray = JsonArray()
|
||||
recipients.forEach { teacher ->
|
||||
recipientsArray += JsonObject(
|
||||
"Address" to teacher.fullNameLastFirst,
|
||||
"LoginId" to (teacher.loginId?.toIntOrNull() ?: return@forEach),
|
||||
"Initials" to teacher.initialsLastFirst,
|
||||
"AddressHash" to teacher.fullNameLastFirst.sha1Hex()
|
||||
)
|
||||
}
|
||||
|
||||
val senderName = (profile?.accountName ?: profile?.studentNameLong)
|
||||
?.swapFirstLastName() ?: ""
|
||||
val sender = JsonObject(
|
||||
"Address" to senderName,
|
||||
"LoginId" to data.studentLoginId.toString(),
|
||||
"Initials" to senderName.getNameInitials(),
|
||||
"AddressHash" to senderName.sha1Hex()
|
||||
)
|
||||
|
||||
apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_MESSAGES_SEND,
|
||||
payload = JsonObject(
|
||||
"Status" to 1,
|
||||
"Sender" to sender,
|
||||
"DateSent" to null,
|
||||
"DateRead" to null,
|
||||
"Content" to text,
|
||||
"Receiver" to recipientsArray,
|
||||
"Id" to 0,
|
||||
"Subject" to subject,
|
||||
"Attachments" to null,
|
||||
"Self" to null
|
||||
)
|
||||
) { json: JsonObject, _ ->
|
||||
val messageId = json.getLong("Id")
|
||||
|
||||
if (messageId == null) {
|
||||
// TODO error
|
||||
return@apiPost
|
||||
}
|
||||
|
||||
VulcanHebeMessages(data, null) {
|
||||
val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject }
|
||||
val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == messageId }
|
||||
val event = MessageSentEvent(data.profileId, message, message?.addedDate)
|
||||
|
||||
EventBus.getDefault().postSticky(event)
|
||||
onSuccess()
|
||||
}.getMessages(Message.TYPE_SENT)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_SOURCE
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_TARGET
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class VulcanHebeTimetable(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeTimetable"
|
||||
}
|
||||
|
||||
private val lessonList = mutableListOf<Lesson>()
|
||||
private val lessonDates = mutableSetOf<Int>()
|
||||
|
||||
init {
|
||||
val previousWeekStart = Week.getWeekStart().stepForward(0, 0, -7)
|
||||
if (Date.getToday().weekDay > 4) {
|
||||
previousWeekStart.stepForward(0, 0, 7)
|
||||
}
|
||||
|
||||
val dateFrom = data.arguments
|
||||
?.getString("weekStart")
|
||||
?.let { Date.fromY_m_d(it) }
|
||||
?: previousWeekStart
|
||||
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
|
||||
|
||||
val lastSync = null
|
||||
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_TIMETABLE,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
dateFrom = dateFrom,
|
||||
dateTo = dateTo,
|
||||
lastSync = lastSync
|
||||
) { lessons, _ ->
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
dateFrom = dateFrom,
|
||||
dateTo = dateTo,
|
||||
lastSync = lastSync
|
||||
) { changes, _ ->
|
||||
processData(lessons, changes)
|
||||
|
||||
// cancel lesson changes when caused by a shift
|
||||
for (lesson in lessonList) {
|
||||
if (lesson.type != TYPE_SHIFTED_TARGET)
|
||||
continue
|
||||
lessonList.firstOrNull {
|
||||
it.oldDate == lesson.date
|
||||
&& it.oldLessonNumber == lesson.lessonNumber
|
||||
&& it.type == TYPE_CHANGE
|
||||
}?.let {
|
||||
it.type = TYPE_CANCELLED
|
||||
it.date = null
|
||||
it.lessonNumber = null
|
||||
it.startTime = null
|
||||
it.endTime = null
|
||||
it.subjectId = null
|
||||
it.teacherId = null
|
||||
it.teamId = null
|
||||
it.classroom = null
|
||||
}
|
||||
}
|
||||
|
||||
// add TYPE_NO_LESSONS to empty dates
|
||||
val date: Date = dateFrom.clone()
|
||||
while (date <= dateTo) {
|
||||
if (!lessonDates.contains(date.value)) {
|
||||
lessonList.add(Lesson(profileId, date.value.toLong()).apply {
|
||||
this.type = Lesson.TYPE_NO_LESSONS
|
||||
this.date = date.clone()
|
||||
})
|
||||
}
|
||||
|
||||
date.stepForward(0, 0, 1)
|
||||
}
|
||||
|
||||
d(
|
||||
TAG,
|
||||
"Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}"
|
||||
)
|
||||
|
||||
data.lessonList.addAll(lessonList)
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_TIMETABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLesson(changes: List<JsonObject>, json: JsonObject): Pair<Lesson, Lesson?>? {
|
||||
val lesson = Lesson(profileId, -1)
|
||||
var lessonShift: Lesson? = null
|
||||
|
||||
val lessonDate = getDate(json, "Date") ?: return null
|
||||
val lessonRange = getLessonRange(json, "TimeSlot")
|
||||
val startTime = lessonRange?.startTime
|
||||
val endTime = lessonRange?.endTime
|
||||
val teacherId = getTeacherId(json, "TeacherPrimary")
|
||||
val classroom = json.getJsonObject("Room").getString("Code")
|
||||
val subjectId = getSubjectId(json, "Subject")
|
||||
|
||||
val teamId = getTeamId(json, "Distribution")
|
||||
?: getClassId(json, "Clazz")
|
||||
?: data.teamClass?.id
|
||||
?: -1
|
||||
|
||||
val change = json.getJsonObject("Change")
|
||||
val changeId = change.getInt("Id")
|
||||
val type = when (change.getInt("Type")) {
|
||||
1 -> TYPE_CANCELLED
|
||||
2 -> TYPE_CHANGE
|
||||
3 -> TYPE_SHIFTED_SOURCE
|
||||
4 -> TYPE_CANCELLED // TODO: 2021-02-21 add showing cancellation reason
|
||||
else -> TYPE_NORMAL
|
||||
}
|
||||
|
||||
lesson.type = type
|
||||
if (type == TYPE_NORMAL) {
|
||||
lesson.date = lessonDate
|
||||
lesson.lessonNumber = lessonRange?.lessonNumber
|
||||
lesson.startTime = startTime
|
||||
lesson.endTime = endTime
|
||||
lesson.subjectId = subjectId
|
||||
lesson.teacherId = teacherId
|
||||
lesson.teamId = teamId
|
||||
lesson.classroom = classroom
|
||||
} else {
|
||||
lesson.oldDate = lessonDate
|
||||
lesson.oldLessonNumber = lessonRange?.lessonNumber
|
||||
lesson.oldStartTime = startTime
|
||||
lesson.oldEndTime = endTime
|
||||
lesson.oldSubjectId = subjectId
|
||||
lesson.oldTeacherId = teacherId
|
||||
lesson.oldTeamId = teamId
|
||||
lesson.oldClassroom = classroom
|
||||
}
|
||||
|
||||
if (type == TYPE_CHANGE || type == TYPE_SHIFTED_SOURCE) {
|
||||
val changeJson = changes.firstOrNull {
|
||||
it.getInt("Id") == changeId
|
||||
} ?: return lesson to null
|
||||
|
||||
val changeLessonDate = getDate(changeJson, "LessonDate") ?: return lesson to null
|
||||
val changeLessonRange = getLessonRange(changeJson, "TimeSlot") ?: lessonRange
|
||||
val changeStartTime = changeLessonRange?.startTime
|
||||
val changeEndTime = changeLessonRange?.endTime
|
||||
val changeTeacherId = getTeacherId(changeJson, "TeacherPrimary") ?: teacherId
|
||||
val changeClassroom = changeJson.getJsonObject("Room").getString("Code") ?: classroom
|
||||
val changeSubjectId = getSubjectId(changeJson, "Subject") ?: subjectId
|
||||
|
||||
val changeTeamId = getTeamId(json, "Distribution")
|
||||
?: getClassId(json, "Clazz")
|
||||
?: teamId
|
||||
|
||||
if (type != TYPE_CHANGE) {
|
||||
/* lesson shifted */
|
||||
lessonShift = Lesson(profileId, -1)
|
||||
lessonShift.type = TYPE_SHIFTED_TARGET
|
||||
|
||||
// update source lesson with the target lesson date
|
||||
lesson.date = changeLessonDate
|
||||
lesson.lessonNumber = changeLessonRange?.lessonNumber
|
||||
lesson.startTime = changeStartTime
|
||||
lesson.endTime = changeEndTime
|
||||
// update target lesson with the source lesson date
|
||||
lessonShift.oldDate = lessonDate
|
||||
lessonShift.oldLessonNumber = lessonRange?.lessonNumber
|
||||
lessonShift.oldStartTime = startTime
|
||||
lessonShift.oldEndTime = endTime
|
||||
}
|
||||
|
||||
(if (type == TYPE_CHANGE) lesson else lessonShift)
|
||||
?.apply {
|
||||
this.date = changeLessonDate
|
||||
this.lessonNumber = changeLessonRange?.lessonNumber
|
||||
this.startTime = changeStartTime
|
||||
this.endTime = changeEndTime
|
||||
this.subjectId = changeSubjectId
|
||||
this.teacherId = changeTeacherId
|
||||
this.teamId = changeTeamId
|
||||
this.classroom = changeClassroom
|
||||
}
|
||||
}
|
||||
|
||||
return lesson to lessonShift
|
||||
}
|
||||
|
||||
private fun processData(lessons: List<JsonObject>, changes: List<JsonObject>) {
|
||||
lessons.forEach { lessonJson ->
|
||||
if (lessonJson.getBoolean("Visible") != true)
|
||||
return@forEach
|
||||
|
||||
val lessonPair = buildLesson(changes, lessonJson) ?: return@forEach
|
||||
val (lessonObject, lessonShift) = lessonPair
|
||||
|
||||
when {
|
||||
lessonShift != null -> lessonShift
|
||||
lessonObject.type != TYPE_NORMAL -> lessonObject
|
||||
else -> null
|
||||
}?.let { lesson ->
|
||||
val lessonDate = lesson.displayDate ?: return@let
|
||||
val seen = profile?.empty ?: true || lessonDate < Date.getToday()
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
lesson.id,
|
||||
seen,
|
||||
seen
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
lessonObject.id = lessonObject.buildId()
|
||||
lessonShift?.id = lessonShift?.buildId() ?: -1
|
||||
|
||||
lessonList.add(lessonObject)
|
||||
lessonShift?.let { lessonList.add(it) }
|
||||
|
||||
lessonObject.displayDate?.let { lessonDates.add(it.value) }
|
||||
lessonShift?.displayDate?.let { lessonDates.add(it.value) }
|
||||
}
|
||||
}
|
||||
}
|
@ -9,9 +9,12 @@ import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMain
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
||||
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.events.FirstLoginFinishedEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -25,6 +28,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
|
||||
private val api = VulcanApi(data, null)
|
||||
private val web = VulcanWebMain(data, null)
|
||||
private val hebe = VulcanHebe(data, null)
|
||||
private val profileList = mutableListOf<Profile>()
|
||||
private val loginStoreId = data.loginStore.id
|
||||
private var firstProfileId = loginStoreId
|
||||
@ -50,12 +54,18 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
checkSymbol(certificate)
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) {
|
||||
registerDevice {
|
||||
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
else {
|
||||
registerDeviceHebe {
|
||||
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSymbol(certificate: CufsCertificate) {
|
||||
@ -103,7 +113,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.apiPin = data.apiPin.toMutableMap().also {
|
||||
it[symbol] = json.getString("PIN")
|
||||
}
|
||||
registerDevice(onSuccess)
|
||||
registerDeviceHebe(onSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,4 +207,21 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerDeviceHebe(onSuccess: () -> Unit) {
|
||||
VulcanLoginHebe(data) {
|
||||
VulcanHebeMain(data).getStudents(
|
||||
profile = null,
|
||||
profileList,
|
||||
loginStoreId,
|
||||
firstProfileId,
|
||||
onEmpty = {
|
||||
EventBus.getDefault()
|
||||
.postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
||||
onSuccess()
|
||||
},
|
||||
onSuccess = onSuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
||||
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
|
||||
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.utils.Utils
|
||||
@ -54,6 +55,10 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
|
||||
VulcanLoginApi(data) { onSuccess(loginMethodId) }
|
||||
}
|
||||
LOGIN_METHOD_VULCAN_HEBE -> {
|
||||
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
|
||||
VulcanLoginHebe(data) { onSuccess(loginMethodId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,18 +191,6 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
val deviceId = data.app.deviceId.padStart(16, '0')
|
||||
val loginStoreId = data.loginStore.id.toString(16).padStart(4, '0')
|
||||
val symbol = data.symbol?.crc16()?.toString(16)?.take(2) ?: "00"
|
||||
val uuid =
|
||||
deviceId.substring(0..7) +
|
||||
"-" + deviceId.substring(8..11) +
|
||||
"-" + deviceId.substring(12..15) +
|
||||
"-" + loginStoreId +
|
||||
"-" + symbol + "6f72616e7a"
|
||||
|
||||
val deviceNameSuffix = " - nie usuwać"
|
||||
|
||||
val szkolnyApi = SzkolnyApi(data.app)
|
||||
val firebaseToken = szkolnyApi.runCatching({
|
||||
getFirebaseToken("vulcan")
|
||||
@ -216,8 +204,8 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
.addHeader("RequestMobileType", "RegisterDevice")
|
||||
.addParameter("PIN", data.apiPin[data.symbol])
|
||||
.addParameter("TokenKey", data.apiToken[data.symbol])
|
||||
.addParameter("DeviceId", uuid)
|
||||
.addParameter("DeviceName", VULCAN_API_DEVICE_NAME.take(50 - deviceNameSuffix.length) + deviceNameSuffix)
|
||||
.addParameter("DeviceId", data.buildDeviceId())
|
||||
.addParameter("DeviceName", VULCAN_API_DEVICE_NAME)
|
||||
.addParameter("DeviceNameUser", "")
|
||||
.addParameter("DeviceDescription", "")
|
||||
.addParameter("DeviceSystemType", "Android")
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import io.github.wulkanowy.signer.hebe.generateKeyPair
|
||||
import pl.szczodrzynski.edziennik.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_DEVICE_NAME
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_REGISTER_NEW
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
|
||||
class VulcanLoginHebe(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
private const val TAG = "VulcanLoginHebe"
|
||||
}
|
||||
|
||||
init { run {
|
||||
// i'm sure this does something useful
|
||||
// not quite sure what, though
|
||||
if (data.studentSemesterNumber == 1 && data.semester1Id == 0)
|
||||
data.semester1Id = data.studentSemesterNumber
|
||||
if (data.studentSemesterNumber == 2 && data.semester2Id == 0)
|
||||
data.semester2Id = data.studentSemesterNumber
|
||||
|
||||
copyFromLoginStore()
|
||||
|
||||
if (data.profile != null && data.isApiLoginValid()) {
|
||||
onSuccess()
|
||||
}
|
||||
else {
|
||||
if (data.symbol.isNotNullNorEmpty() && data.apiToken[data.symbol].isNotNullNorEmpty() && data.apiPin[data.symbol].isNotNullNorEmpty()) {
|
||||
loginWithToken()
|
||||
}
|
||||
else {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
private fun copyFromLoginStore() {
|
||||
data.loginStore.data.apply {
|
||||
// map form inputs to the symbol
|
||||
if (has("symbol")) {
|
||||
data.symbol = getString("symbol")
|
||||
remove("symbol")
|
||||
}
|
||||
if (has("deviceToken")) {
|
||||
data.apiToken = data.apiToken.toMutableMap().also {
|
||||
it[data.symbol] = getString("deviceToken")
|
||||
}
|
||||
remove("deviceToken")
|
||||
}
|
||||
if (has("devicePin")) {
|
||||
data.apiPin = data.apiPin.toMutableMap().also {
|
||||
it[data.symbol] = getString("devicePin")
|
||||
}
|
||||
remove("devicePin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginWithToken() {
|
||||
val szkolnyApi = SzkolnyApi(data.app)
|
||||
val hebe = VulcanHebe(data, null)
|
||||
|
||||
if (data.hebePublicKey == null || data.hebePrivateKey == null || data.hebePublicHash == null) {
|
||||
val (publicPem, privatePem, publicHash) = generateKeyPair()
|
||||
data.hebePublicKey = publicPem
|
||||
data.hebePrivateKey = privatePem
|
||||
data.hebePublicHash = publicHash
|
||||
}
|
||||
|
||||
val firebaseToken = szkolnyApi.runCatching({
|
||||
getFirebaseToken("vulcan")
|
||||
}, onError = {
|
||||
// screw errors
|
||||
}) ?: data.app.config.sync.tokenVulcan
|
||||
|
||||
hebe.apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_REGISTER_NEW,
|
||||
payload = JsonObject(
|
||||
"OS" to "Android",
|
||||
"PIN" to data.apiPin[data.symbol],
|
||||
"Certificate" to data.hebePublicKey,
|
||||
"CertificateType" to "RSA_PEM",
|
||||
"DeviceModel" to VULCAN_API_DEVICE_NAME,
|
||||
"SecurityToken" to data.apiToken[data.symbol],
|
||||
"SelfIdentifier" to data.buildDeviceId(),
|
||||
"CertificateThumbprint" to data.hebePublicHash
|
||||
),
|
||||
baseUrl = true,
|
||||
firebaseToken = firebaseToken
|
||||
) { _: JsonObject, _ ->
|
||||
data.apiToken = data.apiToken.toMutableMap().also {
|
||||
it[data.symbol] = it[data.symbol]?.substring(0, 3)
|
||||
}
|
||||
data.loginStore.removeLoginData("apiPin")
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.fslogin.FSLogin
|
||||
import pl.szczodrzynski.fslogin.realm.CufsRealm
|
||||
import pl.szczodrzynski.fslogin.realm.toRealm
|
||||
|
||||
class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
@ -30,8 +30,7 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
else {
|
||||
if (data.symbol.isNotNullNorEmpty()
|
||||
&& data.webType.isNotNullNorEmpty()
|
||||
&& data.webHost.isNotNullNorEmpty()
|
||||
&& data.webRealmData != null
|
||||
&& (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty())
|
||||
&& data.webPassword.isNotNullNorEmpty()) {
|
||||
try {
|
||||
@ -56,32 +55,28 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.symbol = getString("symbol")
|
||||
remove("symbol")
|
||||
}
|
||||
// 4.6 - form inputs renamed
|
||||
if (has("email")) {
|
||||
data.webEmail = getString("email")
|
||||
remove("email")
|
||||
}
|
||||
if (has("username")) {
|
||||
data.webUsername = getString("username")
|
||||
remove("username")
|
||||
}
|
||||
if (has("password")) {
|
||||
data.webPassword = getString("password")
|
||||
remove("password")
|
||||
}
|
||||
}
|
||||
|
||||
if (data.symbol == null && data.webRealmData != null) {
|
||||
data.symbol = data.webRealmData?.symbol
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginWithCredentials(): Boolean {
|
||||
val realm = when (data.webType) {
|
||||
"cufs" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
)
|
||||
"adfs" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
).toAdfsRealm(id = data.webAdfsId ?: return false)
|
||||
"adfslight" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
).toAdfsLightRealm(
|
||||
id = data.webAdfsId ?: return false,
|
||||
domain = data.webAdfsDomain ?: "adfslight",
|
||||
isScoped = data.webIsScopedAdfs
|
||||
)
|
||||
else -> return false
|
||||
}
|
||||
val realm = data.webRealmData?.toRealm() ?: return false
|
||||
|
||||
val certificate = web.readCertificate()?.let { web.parseCertificate(it) }
|
||||
if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) {
|
||||
@ -89,7 +84,7 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
return true
|
||||
}
|
||||
|
||||
val fsLogin = FSLogin(data.app.http, debug = App.debugMode)
|
||||
val fsLogin = FSLogin(data.app.http, debug = App.devMode)
|
||||
fsLogin.performLogin(
|
||||
realm = realm,
|
||||
username = data.webUsername ?: data.webEmail ?: return false,
|
||||
|
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-9-3.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.events
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
|
||||
data class RegisterAvailabilityEvent(
|
||||
val data: Map< String, RegisterAvailabilityStatus>
|
||||
)
|
@ -136,7 +136,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
||||
val db: AppDb by lazy { app.db }
|
||||
|
||||
init {
|
||||
if (App.devMode) {
|
||||
if (App.debugMode) {
|
||||
fakeLogin = loginStore.hasLoginData("fakeLogin")
|
||||
}
|
||||
clear()
|
||||
|
@ -12,12 +12,14 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
@ -112,6 +114,22 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
private inline fun <reified T> parseResponse(response: Response<ApiResponse<T>>): T {
|
||||
app.config.update = response.body()?.update?.let { update ->
|
||||
if (update.versionCode > BuildConfig.VERSION_CODE) {
|
||||
if (update.updateMandatory
|
||||
&& EventBus.getDefault().hasSubscriberForEvent(update::class.java)) {
|
||||
EventBus.getDefault().postSticky(update)
|
||||
}
|
||||
update
|
||||
}
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
response.body()?.registerAvailability?.let { registerAvailability ->
|
||||
app.config.sync.registerAvailability = registerAvailability
|
||||
}
|
||||
|
||||
if (response.isSuccessful && response.body()?.success == true) {
|
||||
if (Unit is T) {
|
||||
return Unit
|
||||
@ -330,8 +348,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getPlatforms(registerName: String): List<LoginInfo.Platform> {
|
||||
val response = api.appLoginPlatforms(registerName).execute()
|
||||
fun getRealms(registerName: String): List<LoginInfo.Platform> {
|
||||
val response = api.fsLoginRealms(registerName).execute()
|
||||
|
||||
return parseResponse(response)
|
||||
}
|
||||
@ -341,4 +359,10 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
val response = api.firebaseToken(registerName).execute()
|
||||
return parseResponse(response)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getRegisterAvailability(): Map<String, RegisterAvailabilityStatus> {
|
||||
val response = api.registerAvailability().execute()
|
||||
return parseResponse(response)
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,12 @@ interface SzkolnyService {
|
||||
@POST("feedbackMessage")
|
||||
fun feedbackMessage(@Body request: FeedbackMessageRequest): Call<ApiResponse<FeedbackMessageResponse>>
|
||||
|
||||
@GET("appLogin/platforms/{registerName}")
|
||||
fun appLoginPlatforms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
|
||||
|
||||
@GET("firebase/token/{registerName}")
|
||||
fun firebaseToken(@Path("registerName") registerName: String): Call<ApiResponse<String>>
|
||||
|
||||
@GET("registerAvailability")
|
||||
fun registerAvailability(): Call<ApiResponse<Map<String, RegisterAvailabilityStatus>>>
|
||||
|
||||
@GET("fsLogin/{registerName}")
|
||||
fun fsLoginRealms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
|
||||
}
|
||||
|
@ -46,6 +46,6 @@ object Signing {
|
||||
|
||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||
return "$param1.MTIzNDU2Nzg5MDtTu0zYCV===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MDAwogntL3===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,10 @@ data class ApiResponse<T> (
|
||||
|
||||
val errors: List<Error>? = null,
|
||||
|
||||
val data: T? = null
|
||||
val data: T? = null,
|
||||
|
||||
val update: Update? = null,
|
||||
val registerAvailability: Map<String, RegisterAvailabilityStatus>? = null
|
||||
) {
|
||||
data class Error (val code: String, val reason: String)
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-9-2.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
||||
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.DAY
|
||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||
|
||||
data class RegisterAvailabilityStatus(
|
||||
val available: Boolean,
|
||||
val name: String?,
|
||||
val userMessage: Message?,
|
||||
val nextCheckAt: Long = currentTimeUnix() + 7 * DAY,
|
||||
val minVersionCode: Int = BuildConfig.VERSION_CODE
|
||||
) {
|
||||
data class Message(
|
||||
val title: String,
|
||||
val contentShort: String,
|
||||
val contentLong: String,
|
||||
val icon: String?,
|
||||
val image: String?,
|
||||
val url: String?
|
||||
)
|
||||
}
|
@ -11,5 +11,6 @@ data class Update(
|
||||
val releaseNotes: String?,
|
||||
val releaseType: String,
|
||||
val isOnGooglePlay: Boolean,
|
||||
val downloadUrl: String?
|
||||
)
|
||||
val downloadUrl: String?,
|
||||
val updateMandatory: Boolean
|
||||
)
|
||||
|
@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
|
||||
LibrusLesson::class,
|
||||
TimetableManual::class,
|
||||
Metadata::class
|
||||
], version = 88)
|
||||
], version = 89)
|
||||
@TypeConverters(
|
||||
ConverterTime::class,
|
||||
ConverterDate::class,
|
||||
@ -173,7 +173,8 @@ abstract class AppDb : RoomDatabase() {
|
||||
Migration85(),
|
||||
Migration86(),
|
||||
Migration87(),
|
||||
Migration88()
|
||||
Migration88(),
|
||||
Migration89()
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
||||
|
@ -60,4 +60,10 @@ interface ProfileDao {
|
||||
|
||||
@Query("UPDATE profiles SET empty = 0")
|
||||
fun setAllNotEmpty()
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE archiveId = :archiveId AND archived = 1")
|
||||
fun getArchivesOf(archiveId: Int): List<Profile>
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE archiveId = :archiveId AND archived = 0 ORDER BY profileId DESC LIMIT 1")
|
||||
fun getNotArchivedOf(archiveId: Int): Profile?
|
||||
}
|
||||
|
@ -93,6 +93,25 @@ open class Event(
|
||||
var attachmentIds: MutableList<Long>? = null
|
||||
var attachmentNames: MutableList<String>? = null
|
||||
|
||||
/**
|
||||
* Add an attachment
|
||||
* @param id attachment ID
|
||||
* @param name file name incl. extension
|
||||
* @return a Event to which the attachment has been added
|
||||
*/
|
||||
fun addAttachment(id: Long, name: String): Event {
|
||||
if (attachmentIds == null) attachmentIds = mutableListOf()
|
||||
if (attachmentNames == null) attachmentNames = mutableListOf()
|
||||
attachmentIds?.add(id)
|
||||
attachmentNames?.add(name)
|
||||
return this
|
||||
}
|
||||
|
||||
fun clearAttachments() {
|
||||
attachmentIds = null
|
||||
attachmentNames = null
|
||||
}
|
||||
|
||||
@Ignore
|
||||
var showAsUnseen: Boolean? = null
|
||||
|
||||
|
@ -59,6 +59,7 @@ class LoginStore(
|
||||
is Long -> putLoginData(key, o)
|
||||
is Float -> putLoginData(key, o)
|
||||
is Boolean -> putLoginData(key, o)
|
||||
is Bundle -> putLoginData(key, o.toJsonObject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,7 @@ import androidx.room.Ignore
|
||||
import com.google.gson.JsonObject
|
||||
import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.utils.ProfileImageHolder
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.ImageHolder
|
||||
@ -28,7 +27,7 @@ import pl.szczodrzynski.navlib.getDrawableFromRes
|
||||
@Entity(tableName = "profiles", primaryKeys = ["profileId"])
|
||||
open class Profile(
|
||||
@ColumnInfo(name = "profileId")
|
||||
override val id: Int,
|
||||
override var id: Int, /* needs to be var for ProfileArchiver */
|
||||
val loginStoreId: Int,
|
||||
val loginStoreType: Int,
|
||||
|
||||
@ -64,6 +63,12 @@ open class Profile(
|
||||
var empty = true
|
||||
var archived = false
|
||||
|
||||
/**
|
||||
* A unique ID matching [archived] profiles with current ones
|
||||
* and vice-versa.
|
||||
*/
|
||||
var archiveId: Int? = null
|
||||
|
||||
var syncEnabled = true
|
||||
var enableSharedEvents = true
|
||||
var registration = REGISTRATION_UNSPECIFIED
|
||||
@ -85,6 +90,25 @@ open class Profile(
|
||||
@delegate:Ignore
|
||||
val currentSemester by lazy { dateToSemester(Date.getToday()) }
|
||||
|
||||
fun shouldArchive(): Boolean {
|
||||
// vulcan hotfix
|
||||
if (dateYearEnd.month > 6) {
|
||||
dateYearEnd.month = 6
|
||||
dateYearEnd.day = 30
|
||||
}
|
||||
// fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug
|
||||
if (dateSemester1Start.year > studentSchoolYearStart) {
|
||||
val diff = dateSemester1Start.year - studentSchoolYearStart
|
||||
dateSemester1Start.year -= diff
|
||||
dateSemester2Start.year -= diff
|
||||
dateYearEnd.year -= diff
|
||||
}
|
||||
return App.config.archiverEnabled
|
||||
&& Date.getToday() >= dateYearEnd
|
||||
&& Date.getToday().year > studentSchoolYearStart
|
||||
}
|
||||
fun isBeforeYear() = false && Date.getToday() < dateSemester1Start
|
||||
|
||||
var disabledNotifications: List<Long>? = null
|
||||
|
||||
var lastReceiversSync: Long = 0
|
||||
@ -105,15 +129,30 @@ open class Profile(
|
||||
val isParent
|
||||
get() = accountName != null
|
||||
|
||||
val registerName
|
||||
get() = when (loginStoreType) {
|
||||
LOGIN_TYPE_LIBRUS -> "librus"
|
||||
LOGIN_TYPE_VULCAN -> "vulcan"
|
||||
LOGIN_TYPE_IDZIENNIK -> "idziennik"
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
||||
LOGIN_TYPE_PODLASIE -> "podlasie"
|
||||
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getImageDrawable(context: Context): Drawable {
|
||||
if (archived) {
|
||||
return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also {
|
||||
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
||||
}
|
||||
}
|
||||
|
||||
if (!image.isNullOrEmpty()) {
|
||||
try {
|
||||
if (image?.endsWith(".gif", true) == true) {
|
||||
return GifDrawable(image ?: "")
|
||||
}
|
||||
else {
|
||||
return RoundedBitmapDrawableFactory.create(context.resources, image ?: "")
|
||||
return if (image?.endsWith(".gif", true) == true) {
|
||||
GifDrawable(image ?: "")
|
||||
} else {
|
||||
RoundedBitmapDrawableFactory.create(context.resources, image ?: "")
|
||||
//return Drawable.createFromPath(image ?: "") ?: throw Exception()
|
||||
}
|
||||
}
|
||||
@ -125,9 +164,13 @@ open class Profile(
|
||||
return context.getDrawableFromRes(R.drawable.profile).also {
|
||||
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getImageHolder(context: Context): ImageHolder {
|
||||
if (archived) {
|
||||
return ImageHolder(pl.szczodrzynski.edziennik.R.drawable.profile_archived, colorFromName(name))
|
||||
}
|
||||
|
||||
return if (!image.isNullOrEmpty()) {
|
||||
try {
|
||||
ProfileImageHolder(image ?: "")
|
||||
|
@ -11,6 +11,7 @@ import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.fixName
|
||||
import pl.szczodrzynski.edziennik.getNameInitials
|
||||
import pl.szczodrzynski.edziennik.join
|
||||
import java.util.*
|
||||
|
||||
@ -180,6 +181,9 @@ open class Teacher {
|
||||
@delegate:Ignore
|
||||
val fullNameLastFirst by lazy { "$surname $name".fixName() }
|
||||
|
||||
@delegate:Ignore
|
||||
val initialsLastFirst by lazy { fullNameLastFirst.getNameInitials() }
|
||||
|
||||
val shortName: String
|
||||
get() = (if (name == null || name?.length == 0) "" else name!![0].toString()) + "." + surname
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.migration
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration89 : Migration(88, 89) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE profiles ADD COLUMN archiveId INTEGER DEFAULT NULL;")
|
||||
}
|
||||
}
|
@ -5,10 +5,13 @@
|
||||
package pl.szczodrzynski.edziennik.data.firebase
|
||||
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.data.api.task.PostNotifications
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||
@ -50,6 +53,16 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
||||
val message = app.gson.fromJson(message.data.getString("message"), FeedbackMessage::class.java) ?: return@launch
|
||||
feedbackMessage(message)
|
||||
}
|
||||
"registerAvailability" -> launch {
|
||||
val data = app.gson.fromJson<Map<String, RegisterAvailabilityStatus>>(
|
||||
message.data.getString("registerAvailability"),
|
||||
object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type
|
||||
) ?: return@launch
|
||||
app.config.sync.registerAvailability = data
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
||||
EventBus.getDefault().postSticky(RegisterAvailabilityEvent(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
|
||||
val data = message.data.getString("data")?.toJsonObject() ?: return@run
|
||||
val type = data.getString("table") ?: return@run
|
||||
val studentId = data.getInt("pupilid")
|
||||
val loginId = data.getInt("loginid")
|
||||
|
||||
/* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */
|
||||
val viewIdPair = when (type.toLowerCase(Locale.ROOT)) {
|
||||
@ -42,8 +43,9 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
|
||||
}
|
||||
|
||||
val tasks = profiles.filter {
|
||||
it.loginStoreType == LOGIN_TYPE_VULCAN &&
|
||||
it.getStudentData("studentId", 0) == studentId
|
||||
it.loginStoreType == LOGIN_TYPE_VULCAN
|
||||
&& (it.getStudentData("studentId", 0) == studentId
|
||||
|| it.getStudentData("studentLoginId", 0) == loginId)
|
||||
}.map {
|
||||
EdziennikTask.syncProfile(it.id, listOf(viewIdPair))
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
@ -76,7 +77,7 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
|
||||
try {
|
||||
val update = overrideUpdate
|
||||
?: run {
|
||||
val updates = withContext(Dispatchers.Default) {
|
||||
withContext(Dispatchers.Default) {
|
||||
SzkolnyApi(app).runCatching({
|
||||
getUpdate("beta")
|
||||
}, {
|
||||
@ -84,15 +85,25 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
|
||||
})
|
||||
} ?: return@run null
|
||||
|
||||
if (updates.isEmpty()) {
|
||||
if (app.config.update == null
|
||||
|| app.config.update?.versionCode ?: BuildConfig.VERSION_CODE <= BuildConfig.VERSION_CODE) {
|
||||
app.config.update = null
|
||||
Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show()
|
||||
return@run null
|
||||
}
|
||||
updates[0]
|
||||
app.config.update
|
||||
} ?: return
|
||||
|
||||
app.config.update = update
|
||||
if (update.versionCode <= BuildConfig.VERSION_CODE) {
|
||||
app.config.update = null
|
||||
return
|
||||
}
|
||||
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(update::class.java)) {
|
||||
if (!update.updateMandatory) // mandatory updates are posted by the SzkolnyApi
|
||||
EventBus.getDefault().postSticky(update)
|
||||
return
|
||||
}
|
||||
|
||||
val notificationIntent = Intent(app, UpdateDownloaderService::class.java)
|
||||
val pendingIntent = PendingIntent.getService(app, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-9-3.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs
|
||||
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import coil.api.load
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogRegisterUnavailableBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class RegisterUnavailableDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val status: RegisterAvailabilityStatus,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "RegisterUnavailableDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
if (status.available && status.minVersionCode <= BuildConfig.VERSION_CODE)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
app = activity.applicationContext as App
|
||||
|
||||
if (!status.available && status.userMessage != null) {
|
||||
val b = DialogRegisterUnavailableBinding.inflate(LayoutInflater.from(activity), null, false)
|
||||
b.message = status.userMessage
|
||||
if (status.userMessage.image != null)
|
||||
b.image.load(status.userMessage.image)
|
||||
if (status.userMessage.url != null) {
|
||||
b.readMore.onClick {
|
||||
Utils.openUrl(activity, status.userMessage.url)
|
||||
}
|
||||
}
|
||||
b.text.movementMethod = LinkMovementMethod.getInstance()
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setView(b.root)
|
||||
.setPositiveButton(R.string.close) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
return@run
|
||||
}
|
||||
|
||||
val update = app.config.update
|
||||
if (status.minVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (update != null && update.versionCode >= status.minVersionCode) {
|
||||
UpdateAvailableDialog(activity, update, true, onShowListener, onDismissListener)
|
||||
}
|
||||
else {
|
||||
// this *should* never happen
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.update_available_title)
|
||||
.setMessage(R.string.update_available_fallback)
|
||||
.setPositiveButton(R.string.update_available_button) { dialog, _ ->
|
||||
Utils.openGooglePlay(activity)
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setCancelable(false)
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
return@run
|
||||
}
|
||||
}}
|
||||
}
|
@ -17,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
class ServerMessageDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val title: String,
|
||||
val message: String,
|
||||
val message: CharSequence,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-9-3.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs
|
||||
|
||||
import android.text.Html
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateDownloaderService
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class UpdateAvailableDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val update: Update,
|
||||
val mandatory: Boolean = update.updateMandatory,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "UpdateAvailableDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
if (update.versionCode <= BuildConfig.VERSION_CODE)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
app = activity.applicationContext as App
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.update_available_title)
|
||||
.setMessage(
|
||||
R.string.update_available_format,
|
||||
BuildConfig.VERSION_NAME,
|
||||
update.versionName,
|
||||
update.releaseNotes?.let { Html.fromHtml(it) } ?: "---"
|
||||
)
|
||||
.setPositiveButton(R.string.update_available_button) { dialog, _ ->
|
||||
activity.startService(Intent(app, UpdateDownloaderService::class.java))
|
||||
dialog.dismiss()
|
||||
}
|
||||
.also {
|
||||
if (!mandatory)
|
||||
it.setNeutralButton(R.string.update_available_later, null)
|
||||
}
|
||||
.setCancelable(!mandatory)
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
}}
|
||||
}
|
@ -193,7 +193,7 @@ class EventDetailsDialog(
|
||||
b.goToTimetableButton.attachToastHint(R.string.hint_go_to_timetable)
|
||||
|
||||
// RE-DOWNLOAD
|
||||
b.downloadButton.isVisible = App.debugMode
|
||||
b.downloadButton.isVisible = App.devMode
|
||||
b.downloadButton.onClick {
|
||||
EdziennikTask.eventGet(event.profileId, event).enqueue(activity)
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class GradeDetailsDialog(
|
||||
b.grade = grade
|
||||
b.weightText = manager.getWeightString(app, grade)
|
||||
b.commentVisible = false
|
||||
b.devMode = App.debugMode
|
||||
b.devMode = App.devMode
|
||||
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt())
|
||||
b.gradeName.background.setTintColor(gradeColor)
|
||||
|
||||
|
@ -17,7 +17,8 @@ import kotlin.coroutines.CoroutineContext
|
||||
class ProfileRemoveDialog(
|
||||
val activity: MainActivity,
|
||||
val profileId: Int,
|
||||
val profileName: String
|
||||
val profileName: String,
|
||||
val noProfileRemoval: Boolean = false
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ProfileRemoveDialog"
|
||||
@ -52,7 +53,6 @@ class ProfileRemoveDialog(
|
||||
app.db.attendanceDao().clear(profileId)
|
||||
app.db.attendanceTypeDao().clear(profileId)
|
||||
app.db.classroomDao().clear(profileId)
|
||||
app.db.configDao().clear(profileId)
|
||||
app.db.endpointTimerDao().clear(profileId)
|
||||
app.db.eventDao().clear(profileId)
|
||||
app.db.eventTypeDao().clear(profileId)
|
||||
@ -65,23 +65,27 @@ class ProfileRemoveDialog(
|
||||
app.db.messageRecipientDao().clear(profileId)
|
||||
app.db.noticeDao().clear(profileId)
|
||||
app.db.noticeTypeDao().clear(profileId)
|
||||
app.db.noticeTypeDao().clear(profileId)
|
||||
app.db.notificationDao().clear(profileId)
|
||||
app.db.subjectDao().clear(profileId)
|
||||
app.db.teacherAbsenceDao().clear(profileId)
|
||||
app.db.teacherAbsenceDao().clear(profileId)
|
||||
app.db.teacherAbsenceTypeDao().clear(profileId)
|
||||
app.db.teacherDao().clear(profileId)
|
||||
app.db.teamDao().clear(profileId)
|
||||
app.db.timetableDao().clear(profileId)
|
||||
|
||||
app.db.metadataDao().deleteAll(profileId)
|
||||
|
||||
if (noProfileRemoval)
|
||||
return@async
|
||||
|
||||
app.db.configDao().clear(profileId)
|
||||
|
||||
val loginStoreId = profileObject.loginStoreId
|
||||
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
|
||||
if (profilesUsingLoginStore.size == 1) {
|
||||
app.db.loginStoreDao().remove(loginStoreId)
|
||||
}
|
||||
app.db.profileDao().remove(profileId)
|
||||
app.db.metadataDao().deleteAll(profileId)
|
||||
|
||||
if (App.profileId == profileId) {
|
||||
app.profileLoadLast { }
|
||||
|
@ -78,7 +78,7 @@ class LessonDetailsDialog(
|
||||
)
|
||||
}
|
||||
|
||||
if (App.debugMode)
|
||||
if (App.devMode)
|
||||
b.lessonId.visibility = View.VISIBLE
|
||||
|
||||
update()
|
||||
|
@ -55,7 +55,7 @@ class AttendanceDetailsDialog(
|
||||
|
||||
val attendanceColor = manager.getAttendanceColor(attendance)
|
||||
b.attendance = attendance
|
||||
b.devMode = App.debugMode
|
||||
b.devMode = App.devMode
|
||||
b.attendanceName.setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt())
|
||||
b.attendanceName.background.setTintColor(attendanceColor)
|
||||
|
||||
|
@ -167,7 +167,7 @@ class CrashActivity : AppCompatActivity(), CoroutineScope {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
clipboard?.apply {
|
||||
val clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation)
|
||||
primaryClip = clip
|
||||
setPrimaryClip(clip)
|
||||
Toast.makeText(this@CrashActivity, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||
import pl.szczodrzynski.fslogin.decode
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -44,6 +46,8 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
override fun onPageCreated(): Boolean {
|
||||
b.app = app
|
||||
|
||||
b.last10unseen.onClick {
|
||||
launch(Dispatchers.Default) {
|
||||
val events = app.db.eventDao().getAllNow(App.profileId)
|
||||
@ -57,11 +61,35 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
b.rodo.onClick {
|
||||
app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}"))
|
||||
}
|
||||
|
||||
b.fullSync.onClick {
|
||||
app.db.query(SimpleSQLiteQuery("UPDATE profiles SET empty = 1 WHERE profileId = ${App.profileId}"))
|
||||
app.db.query(SimpleSQLiteQuery("DELETE FROM endpointTimers WHERE profileId = ${App.profileId}"))
|
||||
}
|
||||
|
||||
b.clearProfile.onClick {
|
||||
ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true)
|
||||
}
|
||||
|
||||
b.removeHomework.onClick {
|
||||
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
||||
}
|
||||
|
||||
b.unarchive.onClick {
|
||||
app.profile.archived = false
|
||||
app.profile.archiveId = null
|
||||
app.profileSave()
|
||||
}
|
||||
|
||||
val profiles = app.db.profileDao().allNow
|
||||
b.profile.clear()
|
||||
b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) }
|
||||
b.profile.select(app.profileId.toLong())
|
||||
b.profile.setOnChangeListener {
|
||||
activity.loadProfile(it.id.toInt())
|
||||
return@setOnChangeListener true
|
||||
}
|
||||
|
||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||
startCoroutineTimer(500L, 300L) {
|
||||
val text = app.cookieJar.sessionCookies
|
||||
|
@ -45,7 +45,7 @@ class ErrorDetailsDialog(
|
||||
listOf(
|
||||
it.getStringReason(activity).asBoldSpannable().asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
|
||||
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
|
||||
if (App.debugMode)
|
||||
if (App.devMode)
|
||||
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
|
||||
else
|
||||
it.throwable?.localizedMessage
|
||||
|
@ -71,9 +71,14 @@ class GradesListFragment : Fragment(), CoroutineScope {
|
||||
val adapter = GradesAdapter(activity)
|
||||
var firstRun = true
|
||||
|
||||
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this@GradesListFragment, Observer { items -> this@GradesListFragment.launch {
|
||||
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this@GradesListFragment, Observer { grades -> this@GradesListFragment.launch {
|
||||
if (!isAdded) return@launch
|
||||
|
||||
val items = when {
|
||||
app.config.forProfile().grades.hideSticksFromOld && App.devMode -> grades.filter { it.value != 1.0f }
|
||||
else -> grades
|
||||
}
|
||||
|
||||
// load & configure the adapter
|
||||
adapter.items = withContext(Dispatchers.Default) { processGrades(items) }
|
||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||
|
@ -33,9 +33,10 @@ class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, priv
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
removeCard(viewHolder.adapterPosition)
|
||||
cardAdapter.items.removeAt(viewHolder.adapterPosition)
|
||||
cardAdapter.notifyItemRemoved(viewHolder.adapterPosition)
|
||||
val position = viewHolder.adapterPosition
|
||||
removeCard(position, cardAdapter)
|
||||
cardAdapter.items.removeAt(position)
|
||||
cardAdapter.notifyItemRemoved(position)
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
|
@ -21,17 +21,11 @@ import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon
|
||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeEventsCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.*
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||
@ -44,8 +38,8 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter): Boolean {
|
||||
val fromCard = cardAdapter.items[fromPosition]
|
||||
val toCard = cardAdapter.items[toPosition]
|
||||
if (fromCard.id == 100 || toCard.id == 100) {
|
||||
// debug card is not swappable
|
||||
if (fromCard.id >= 100 || toCard.id >= 100) {
|
||||
// debug & archive cards are not swappable
|
||||
return false
|
||||
}
|
||||
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
|
||||
@ -53,18 +47,26 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||
cardAdapter.notifyItemMoved(fromPosition, toPosition)
|
||||
|
||||
val homeCards = App.config.forProfile().ui.homeCards.toMutableList()
|
||||
val fromPair = homeCards[fromPosition]
|
||||
homeCards[fromPosition] = homeCards[toPosition]
|
||||
homeCards[toPosition] = fromPair
|
||||
val fromIndex = homeCards.indexOfFirst { it.cardId == fromCard.id }
|
||||
val toIndex = homeCards.indexOfFirst { it.cardId == toCard.id }
|
||||
val fromPair = homeCards[fromIndex]
|
||||
homeCards[fromIndex] = homeCards[toIndex]
|
||||
homeCards[toIndex] = fromPair
|
||||
App.config.forProfile().ui.homeCards = homeCards
|
||||
return true
|
||||
}
|
||||
|
||||
fun removeCard(position: Int) {
|
||||
fun removeCard(position: Int, cardAdapter: HomeCardAdapter) {
|
||||
val homeCards = App.config.forProfile().ui.homeCards.toMutableList()
|
||||
if (position >= homeCards.size)
|
||||
return
|
||||
homeCards.removeAt(position)
|
||||
val card = cardAdapter.items[position]
|
||||
if (card.id >= 100) {
|
||||
// debug & archive cards are not removable
|
||||
//cardAdapter.notifyDataSetChanged()
|
||||
return
|
||||
}
|
||||
homeCards.removeAll { it.cardId == card.id }
|
||||
App.config.forProfile().ui.homeCards = homeCards
|
||||
}
|
||||
}
|
||||
@ -150,16 +152,26 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||
|
||||
val items = mutableListOf<HomeCard>()
|
||||
cards.mapNotNullTo(items) {
|
||||
@Suppress("USELESS_CAST")
|
||||
when (it.cardId) {
|
||||
HomeCard.CARD_LUCKY_NUMBER -> HomeLuckyNumberCard(it.cardId, app, activity, this, app.profile)
|
||||
HomeCard.CARD_TIMETABLE -> HomeTimetableCard(it.cardId, app, activity, this, app.profile)
|
||||
HomeCard.CARD_GRADES -> HomeGradesCard(it.cardId, app, activity, this, app.profile)
|
||||
HomeCard.CARD_EVENTS -> HomeEventsCard(it.cardId, app, activity, this, app.profile)
|
||||
else -> null
|
||||
}
|
||||
} as HomeCard?
|
||||
}
|
||||
//if (App.devMode)
|
||||
// items += HomeDebugCard(100, app, activity, this, app.profile)
|
||||
if (app.profile.archived)
|
||||
items.add(0, HomeArchiveCard(101, app, activity, this, app.profile))
|
||||
|
||||
val status = app.config.sync.registerAvailability[app.profile.registerName]
|
||||
val update = app.config.update
|
||||
if (update != null && update.versionCode > BuildConfig.VERSION_CODE
|
||||
|| status != null && (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)) {
|
||||
items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile))
|
||||
}
|
||||
|
||||
val adapter = HomeCardAdapter(items)
|
||||
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-8-25.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home.cards
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.plusAssign
|
||||
import androidx.core.view.setMargins
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.CardHomeArchiveBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class HomeArchiveCard(
|
||||
override val id: Int,
|
||||
val app: App,
|
||||
val activity: MainActivity,
|
||||
val fragment: HomeFragment,
|
||||
val profile: Profile
|
||||
) : HomeCard, CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "HomeArchiveCard"
|
||||
}
|
||||
|
||||
private var job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
|
||||
holder.root.removeAllViews()
|
||||
val b = CardHomeArchiveBinding.inflate(LayoutInflater.from(holder.root.context))
|
||||
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(8.dp)
|
||||
}
|
||||
holder.root += b.root
|
||||
|
||||
b.homeArchiveText.setText(
|
||||
R.string.home_archive_text,
|
||||
profile.studentSchoolYearStart,
|
||||
profile.studentSchoolYearStart + 1
|
||||
)
|
||||
|
||||
b.homeArchiveClose.onClick {
|
||||
launch {
|
||||
val profile = profile.archiveId?.let {
|
||||
withContext(Dispatchers.IO) {
|
||||
app.db.profileDao().getNotArchivedOf(it)
|
||||
}
|
||||
}
|
||||
if (profile == null) {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.home_archive_close_no_target_title)
|
||||
.setMessage(R.string.home_archive_close_no_target_text, this@HomeArchiveCard.profile.name)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
activity.drawer.profileSelectionOpen()
|
||||
activity.drawer.open()
|
||||
}
|
||||
.show()
|
||||
return@launch
|
||||
}
|
||||
activity.loadProfile(profile)
|
||||
}
|
||||
}
|
||||
|
||||
holder.root.onClick {
|
||||
activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA)
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-9-3.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home.cards
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.plusAssign
|
||||
import androidx.core.view.setMargins
|
||||
import coil.api.load
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.CardHomeAvailabilityBinding
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateDownloaderService
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.UpdateAvailableDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class HomeAvailabilityCard(
|
||||
override val id: Int,
|
||||
val app: App,
|
||||
val activity: MainActivity,
|
||||
val fragment: HomeFragment,
|
||||
val profile: Profile
|
||||
) : HomeCard, CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "HomeAvailabilityCard"
|
||||
}
|
||||
|
||||
private var job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
|
||||
holder.root.removeAllViews()
|
||||
val b = CardHomeAvailabilityBinding.inflate(LayoutInflater.from(holder.root.context))
|
||||
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(8.dp)
|
||||
}
|
||||
holder.root += b.root
|
||||
|
||||
val status = app.config.sync.registerAvailability[profile.registerName]
|
||||
val update = app.config.update
|
||||
|
||||
if (update == null && status == null)
|
||||
return
|
||||
|
||||
var onInfoClick = { _: View -> }
|
||||
|
||||
if (status != null && !status.available && status.userMessage != null) {
|
||||
b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
b.homeAvailabilityUpdate.isVisible = false
|
||||
b.homeAvailabilityIcon.setImageResource(R.drawable.ic_sync)
|
||||
if (status.userMessage.icon != null)
|
||||
b.homeAvailabilityIcon.load(status.userMessage.icon)
|
||||
onInfoClick = {
|
||||
RegisterUnavailableDialog(activity, status)
|
||||
}
|
||||
}
|
||||
else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) {
|
||||
b.homeAvailabilityTitle.setText(R.string.home_availability_title)
|
||||
b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName)
|
||||
b.homeAvailabilityUpdate.isVisible = true
|
||||
b.homeAvailabilityIcon.setImageResource(R.drawable.ic_update)
|
||||
onInfoClick = {
|
||||
UpdateAvailableDialog(activity, update)
|
||||
}
|
||||
}
|
||||
|
||||
b.homeAvailabilityUpdate.onClick {
|
||||
if (update == null)
|
||||
return@onClick
|
||||
activity.startService(Intent(app, UpdateDownloaderService::class.java))
|
||||
}
|
||||
|
||||
b.homeAvailabilityInfo.onClick(onInfoClick)
|
||||
holder.root.onClick(onInfoClick)
|
||||
}
|
||||
|
||||
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
|
||||
}
|
@ -25,7 +25,6 @@ import kotlin.coroutines.CoroutineContext
|
||||
class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "LoginActivity"
|
||||
var thisOneIsTricky = 0
|
||||
}
|
||||
|
||||
private val app: App by lazy { applicationContext as App }
|
||||
@ -42,6 +41,8 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
val profiles = mutableListOf<LoginSummaryAdapter.Item>()
|
||||
val loginStores = mutableListOf<LoginStore>()
|
||||
|
||||
fun getRootView() = b.root
|
||||
|
||||
override fun onBackPressed() {
|
||||
val destination = nav.currentDestination ?: run {
|
||||
nav.navigateUp()
|
||||
@ -55,6 +56,11 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
return
|
||||
if (destination.id == R.id.loginFinishFragment)
|
||||
return
|
||||
// eggs
|
||||
if (destination.id == R.id.loginPrizeFragment) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
@ -79,8 +85,6 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
super.onCreate(savedInstanceState)
|
||||
setTheme(R.style.AppTheme_Light)
|
||||
|
||||
thisOneIsTricky = -1
|
||||
|
||||
navOptions = NavOptions.Builder()
|
||||
.setEnterAnim(R.anim.slide_in_right)
|
||||
.setExitAnim(R.anim.slide_out_left)
|
||||
|
@ -60,7 +60,8 @@ class LoginChooserAdapter(
|
||||
|
||||
private val onClickListener = View.OnClickListener { view ->
|
||||
val model = view.getTag(R.string.tag_key_model)
|
||||
if (model is LoginInfo.Register && model.loginModes.size == 1) {
|
||||
if (model is LoginInfo.Register
|
||||
&& model.loginModes.count { App.devMode || !it.isDevOnly } == 1) {
|
||||
onModeClick?.invoke(model, model.loginModes.first())
|
||||
return@OnClickListener
|
||||
}
|
||||
@ -85,7 +86,9 @@ class LoginChooserAdapter(
|
||||
|
||||
if (model.state == STATE_CLOSED) {
|
||||
|
||||
val subItems = model.items
|
||||
val subItems = model.items.filter {
|
||||
App.devMode || !it.isDevOnly
|
||||
}
|
||||
|
||||
model.state = STATE_OPENED
|
||||
items.addAll(position + 1, subItems)
|
||||
|
@ -4,23 +4,28 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.login
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.RotateAnimation
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.Bundle
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
|
||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -28,6 +33,8 @@ import kotlin.coroutines.CoroutineContext
|
||||
class LoginChooserFragment : Fragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "LoginChooserFragment"
|
||||
// eggs
|
||||
var isRotated = false
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
@ -49,26 +56,33 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
||||
return b.root
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (!isAdded) return
|
||||
|
||||
val adapter = LoginChooserAdapter(activity) { loginType, loginMode ->
|
||||
if (loginMode.isPlatformSelection) {
|
||||
nav.navigate(R.id.loginPlatformListFragment, Bundle(
|
||||
"loginType" to loginType.loginType,
|
||||
"loginMode" to loginMode.loginMode
|
||||
), activity.navOptions)
|
||||
return@LoginChooserAdapter
|
||||
}
|
||||
|
||||
nav.navigate(R.id.loginFormFragment, Bundle(
|
||||
"loginType" to loginType.loginType,
|
||||
"loginMode" to loginMode.loginMode
|
||||
), activity.navOptions)
|
||||
}
|
||||
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
|
||||
|
||||
LoginInfo.chooserList = LoginInfo.chooserList
|
||||
?: LoginInfo.list.toMutableList<Any>()
|
||||
?: LoginInfo.list.toMutableList()
|
||||
|
||||
// eggs
|
||||
if (isRotated) {
|
||||
isRotated = false
|
||||
LoginFormFragment.wantEggs = false
|
||||
LoginInfo.chooserList = LoginInfo.list.toMutableList()
|
||||
val anim = RotateAnimation(
|
||||
180f,
|
||||
0f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||
anim.duration = 500
|
||||
anim.fillAfter = true
|
||||
activity.getRootView().startAnimation(anim)
|
||||
}
|
||||
|
||||
adapter.items = LoginInfo.chooserList!!
|
||||
b.list.adapter = adapter
|
||||
@ -82,6 +96,81 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
||||
startActivity(Intent(activity, FeedbackActivity::class.java))
|
||||
}
|
||||
|
||||
// eggs
|
||||
b.footnoteText.onClick {
|
||||
if (!LoginFormFragment.wantEggs || isRotated)
|
||||
return@onClick
|
||||
|
||||
val text = b.subtitleText.text.toString()
|
||||
if (text.endsWith(".."))
|
||||
b.subtitleText.text = text.substring(0, text.length - 2)
|
||||
else
|
||||
b.subtitleText.text = "$text..."
|
||||
}
|
||||
var clickCount = 0
|
||||
val color = R.color.md_blue_500.resolveColor(app)
|
||||
val hsv = FloatArray(3)
|
||||
Color.colorToHSV(color, hsv)
|
||||
val hueOriginal = hsv[0]
|
||||
b.subtitleText.onClick {
|
||||
if (isRotated)
|
||||
return@onClick
|
||||
val text = b.subtitleText.text.toString()
|
||||
if (text.endsWith("..") && !text.endsWith("...")) {
|
||||
clickCount++
|
||||
}
|
||||
if (clickCount == 5) {
|
||||
val anim = ValueAnimator.ofFloat(0f, 1f)
|
||||
anim.duration = 5000
|
||||
anim.addUpdateListener {
|
||||
hsv[0] = hueOriginal + it.animatedFraction * 3f * 360f
|
||||
hsv[0] = hsv[0] % 360f
|
||||
b.topLogo.drawable.setTintColor(Color.HSVToColor(Color.alpha(color), hsv))
|
||||
}
|
||||
anim.start()
|
||||
}
|
||||
}
|
||||
b.topLogo.onClick {
|
||||
if (clickCount != 5 || isRotated) {
|
||||
clickCount = 0
|
||||
return@onClick
|
||||
}
|
||||
isRotated = true
|
||||
val anim = RotateAnimation(
|
||||
0f,
|
||||
180f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||
anim.duration = 2000
|
||||
anim.fillAfter = true
|
||||
activity.getRootView().startAnimation(anim)
|
||||
|
||||
b.list.smoothScrollToPosition(0)
|
||||
adapter.items.add(
|
||||
LoginInfo.Register(
|
||||
loginType = 74,
|
||||
internalName = "eggs",
|
||||
registerName = R.string.eggs,
|
||||
registerLogo = R.drawable.face_1,
|
||||
loginModes = listOf(
|
||||
LoginInfo.Mode(
|
||||
loginMode = 0,
|
||||
name = 0,
|
||||
icon = 0,
|
||||
guideText = 0,
|
||||
credentials = listOf(),
|
||||
errorCodes = mapOf()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
adapter.notifyItemInserted(adapter.items.size - 1)
|
||||
}
|
||||
|
||||
when {
|
||||
activity.loginStores.isNotEmpty() -> {
|
||||
// we are navigated here from LoginSummary
|
||||
@ -102,4 +191,79 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLoginModeClicked(
|
||||
loginType: LoginInfo.Register,
|
||||
loginMode: LoginInfo.Mode
|
||||
) {
|
||||
if (loginType.internalName == "eggs") {
|
||||
nav.navigate(R.id.loginEggsFragment, null, activity.navOptions)
|
||||
return
|
||||
}
|
||||
|
||||
launch {
|
||||
if (!checkAvailability(loginType.loginType))
|
||||
return@launch
|
||||
|
||||
if (loginMode.isTesting || loginMode.isDevOnly) {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.login_chooser_testing_title)
|
||||
.setMessage(R.string.login_chooser_testing_text)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
navigateToLoginMode(loginType, loginMode)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
return@launch
|
||||
}
|
||||
|
||||
navigateToLoginMode(loginType, loginMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToLoginMode(loginType: LoginInfo.Register, loginMode: LoginInfo.Mode) {
|
||||
if (loginMode.isPlatformSelection) {
|
||||
nav.navigate(R.id.loginPlatformListFragment, Bundle(
|
||||
"loginType" to loginType.loginType,
|
||||
"loginMode" to loginMode.loginMode
|
||||
), activity.navOptions)
|
||||
return
|
||||
}
|
||||
|
||||
nav.navigate(R.id.loginFormFragment, Bundle(
|
||||
"loginType" to loginType.loginType,
|
||||
"loginMode" to loginMode.loginMode
|
||||
), activity.navOptions)
|
||||
}
|
||||
|
||||
private suspend fun checkAvailability(loginType: Int): Boolean {
|
||||
when (loginType) {
|
||||
LOGIN_TYPE_LIBRUS -> "librus"
|
||||
LOGIN_TYPE_VULCAN -> "vulcan"
|
||||
LOGIN_TYPE_IDZIENNIK -> "idziennik"
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
||||
LOGIN_TYPE_PODLASIE -> "podlasie"
|
||||
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
||||
else -> null
|
||||
}?.let { registerName ->
|
||||
var status = app.config.sync.registerAvailability[registerName]
|
||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val api = SzkolnyApi(app)
|
||||
api.runCatching(activity) {
|
||||
val availability = getRegisterAvailability()
|
||||
app.config.sync.registerAvailability = availability
|
||||
status = availability[registerName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status?.available != true) {
|
||||
if (status != null)
|
||||
RegisterUnavailableDialog(activity, status!!)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-10-18.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.RotateAnimation
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.md5
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class LoginEggsFragment : Fragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "LoginEggsFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: LoginActivity
|
||||
private lateinit var view: ViewGroup
|
||||
private lateinit var webView: WebView
|
||||
private val nav by lazy { activity.nav }
|
||||
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local/private variables go here
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as LoginActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
webView = WebView(activity)
|
||||
view = FrameLayout(activity)
|
||||
view.addView(webView)
|
||||
return view
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (!isAdded) return
|
||||
|
||||
if (!LoginChooserFragment.isRotated) {
|
||||
nav.navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
val anim = RotateAnimation(
|
||||
180f,
|
||||
0f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||
anim.duration = 10
|
||||
anim.fillAfter = true
|
||||
activity.getRootView().startAnimation(anim)
|
||||
|
||||
webView.apply {
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
}
|
||||
addJavascriptInterface(object : Any() {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
@JavascriptInterface
|
||||
fun getPrize() {
|
||||
val anim = RotateAnimation(
|
||||
0f,
|
||||
180f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||
anim.duration = 10
|
||||
anim.fillAfter = true
|
||||
activity.getRootView().startAnimation(anim)
|
||||
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
|
||||
}
|
||||
}, "EggInterface")
|
||||
loadUrl("https://szkolny.eu/game/runner.html")
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
val deviceId = app.deviceId.md5()
|
||||
val version = BuildConfig.VERSION_NAME
|
||||
val js = """initPage("$deviceId", true, "$version");"""
|
||||
webView.evaluateJavascript(js) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user