diff --git a/app/build.gradle b/app/build.gradle
index 57982f62..7ee81956 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -58,6 +58,7 @@ android {
dataBinding = true
}
compileOptions {
+ coreLibraryDesugaringEnabled true
sourceCompatibility '1.8'
targetCompatibility '1.8'
}
@@ -105,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"
@@ -181,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}"
diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html
index 50650cdd..165582ec 100644
--- a/app/src/main/assets/pl-changelog.html
+++ b/app/src/main/assets/pl-changelog.html
@@ -1,10 +1,8 @@
-
Wersja 4.4.3, 2020-10-16
+Wersja 4.5-beta.2, 2021-02-21
- - Mobidziennik: naprawione wysyłanie wiadomości.
- - Vulcan: naprawione logowanie dla dzienników w Koszalinie.
- - PPE: opcja wylogowania innych urządzeń przy logowaniu.
+ - Vulcan: aplikacja Szkolny.eu zaktualizowana w związku z wygaszeniem aplikacji Dzienniczek+.
Dzięki za korzystanie ze Szkolnego!
-© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020
+© Kuba Szczodrzyński, Kacper Ziubryniewicz 2021
diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp
index 4861d399..b2b15406 100644
--- a/app/src/main/cpp/szkolny-signing.cpp
+++ b/app/src/main/cpp/szkolny-signing.cpp
@@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
- 0xaa, 0x6d, 0x87, 0x46, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ 0x42, 0xf5, 0x8e, 0x53, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
index 9a2f84d6..88bdc603 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
@@ -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()
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
index f9e9d474..d4c6a2e0 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
@@ -96,30 +96,30 @@ fun List.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 }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
index 0bbc77f7..d5a1adde 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
@@ -38,6 +38,7 @@ 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
@@ -64,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
@@ -756,6 +758,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
mainSnackbar.dismiss()
errorSnackbar.addError(event.error).show()
+ if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
+ ErrorDetailsDialog(this, listOf(event.error))
+ }
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
index 2d7dcf78..068400a1 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
@@ -99,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? = null
var tokenMobidziennikList: List
@@ -112,6 +116,10 @@ class ConfigSync(private val config: Config) {
var tokenVulcanList: List
get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
+ private var mTokenVulcanHebeList: List? = null
+ var tokenVulcanHebeList: List
+ get() { mTokenVulcanHebeList = mTokenVulcanHebeList ?: config.values.getIntList("tokenVulcanHebeList", listOf()); return mTokenVulcanHebeList ?: listOf() }
+ set(value) { config.set("tokenVulcanHebeList", value); mTokenVulcanHebeList = value }
private var mRegisterAvailability: Map? = null
var registerAvailability: Map
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt
index 5bc08159..614d5f3a 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt
@@ -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,6 +125,17 @@ 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_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_HOMEWORK = "api/mobile/homework"
+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 EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt
index 8301f62f..6809ed2e 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt
@@ -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
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt
index 24444a58..d70dd54e 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt
@@ -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,9 +120,19 @@ val vulcanLoginMethods = listOf(
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
- .withIsPossible { _, _ -> true }
+ .withIsPossible { _, loginStore ->
+ loginStore.mode != LOGIN_MODE_VULCAN_HEBE
+ }
.withRequiredLoginMethod { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
+ },
+
+ LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
+ .withIsPossible { _, loginStore ->
+ loginStore.mode != LOGIN_MODE_VULCAN_API
+ }
+ .withRequiredLoginMethod { _, loginStore ->
+ if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
}
)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
index 882a2fa4..90ba3dd6 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
@@ -4,16 +4,16 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan
-import pl.szczodrzynski.edziennik.App
-import pl.szczodrzynski.edziennik.currentTimeUnix
+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
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
@@ -26,17 +26,27 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
&& 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)) {
@@ -227,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/"
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt
index 9761343c..9dfc1572 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt
@@ -13,6 +13,7 @@ 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.firstlogin.VulcanFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
@@ -91,6 +92,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) {
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
index ab032e0c..9e247575 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
@@ -20,25 +20,46 @@ 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_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_HOMEWORK = 3060
+const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080
+const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090
+const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100
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
+ ), 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
@@ -47,6 +68,9 @@ val VulcanFeatures = listOf(
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 +78,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(
@@ -72,7 +102,11 @@ val VulcanFeatures = listOf(
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)),
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
index 491e8ea6..2fc21190 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
@@ -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,35 @@ 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_ADDRESSBOOK,
+ ENDPOINT_VULCAN_HEBE_TIMETABLE,
+ ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
+ ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
+ )
+
+ 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 +60,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 +135,51 @@ 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_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_HOMEWORK -> {
+ data.startProgress(R.string.edziennik_progress_endpoint_homework)
+ VulcanHebeHomework(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_ATTENDANCE -> {
+ data.startProgress(R.string.edziennik_progress_endpoint_attendance)
+ VulcanHebeAttendance(data, lastSync, onSuccess)
+ }
else -> onSuccess(endpointId)
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt
new file mode 100644
index 00000000..c61140c4
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanHebe.kt
@@ -0,0 +1,360 @@
+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
+ }
+
+ inline fun 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 = 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 apiGet(
+ tag: String,
+ endpoint: String,
+ query: Map = 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 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 = mapOf(),
+ includeFilterType: Boolean = true,
+ onSuccess: (data: List, 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()
+
+ query["lastId"] = "-2147483648" // don't ask, it's just Vulcan
+ query["pageSize"] = "500"
+ query["lastSyncDate"] = LocalDateTime
+ .ofInstant(Instant.ofEpochMilli(lastSync ?: 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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/HebeFilterType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/HebeFilterType.kt
new file mode 100644
index 00000000..6734ab71
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/HebeFilterType.kt
@@ -0,0 +1,7 @@
+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")
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt
new file mode 100644
index 00000000..c96142aa
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAddressbook.kt
@@ -0,0 +1,118 @@
+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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt
new file mode 100644
index 00000000..3d207388
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.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
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt
new file mode 100644
index 00000000..54388a86
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeExams.kt
@@ -0,0 +1,78 @@
+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() ?: ""
+
+ 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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt
new file mode 100644
index 00000000..11a7fdbc
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeGrades.kt
@@ -0,0 +1,121 @@
+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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt
new file mode 100644
index 00000000..9ab8ede0
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeHomework.kt
@@ -0,0 +1,69 @@
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
+
+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.getLong
+import pl.szczodrzynski.edziennik.getString
+
+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() ?: ""
+
+ 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
+ )
+
+ 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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt
new file mode 100644
index 00000000..b3d88d04
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt
@@ -0,0 +1,160 @@
+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?,
+ 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()
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt
new file mode 100644
index 00000000..22fb39b7
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessages.kt
@@ -0,0 +1,127 @@
+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.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)
+
+ val messageObject = Message(
+ profileId = profileId,
+ id = id,
+ type = messageType,
+ subject = subject,
+ body = body.replace("\n", "
"),
+ 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)
+ }
+
+ 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)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt
new file mode 100644
index 00000000..74d3f759
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMessagesChangeStatus.kt
@@ -0,0 +1,62 @@
+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()
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt
new file mode 100644
index 00000000..48d06018
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt
@@ -0,0 +1,247 @@
+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()
+ private val lessonDates = mutableSetOf()
+
+ 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, json: JsonObject): Pair? {
+ 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, changes: List) {
+ 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) }
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
index ed541c69..02d8b9bd 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
@@ -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()
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
+ )
+ }
+ }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
index 45c0153d..b92dea88 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
@@ -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) }
+ }
}
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
index ec84cd05..def1d7e3 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
@@ -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")
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginHebe.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginHebe.kt
new file mode 100644
index 00000000..1308f449
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginHebe.kt
@@ -0,0 +1,106 @@
+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()
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt
index f8ca5090..b15b6408 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt
@@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
- return "$param1.MTIzNDU2Nzg5MDzyYb9Lof===.$param2".sha256()
+ return "$param1.MTIzNDU2Nzg5MDL9U0lwJn===.$param2".sha256()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt
index 2b3acd68..13fa6123 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt
@@ -131,9 +131,9 @@ object LoginInfo {
registerLogo = R.drawable.login_logo_vulcan,
loginModes = listOf(
Mode(
- loginMode = LOGIN_MODE_VULCAN_API,
+ loginMode = LOGIN_MODE_VULCAN_HEBE,
name = R.string.login_mode_vulcan_api,
- icon = R.drawable.login_mode_vulcan_api,
+ icon = R.drawable.login_mode_vulcan_hebe,
hintText = R.string.login_mode_vulcan_api_hint,
guideText = R.string.login_mode_vulcan_api_guide,
isRecommended = true,
diff --git a/app/src/main/res/drawable/login_mode_vulcan_hebe.png b/app/src/main/res/drawable/login_mode_vulcan_hebe.png
new file mode 100644
index 00000000..bea96dc7
Binary files /dev/null and b/app/src/main/res/drawable/login_mode_vulcan_hebe.png differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 461bdbd3..6e37bf58 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -854,7 +854,7 @@
Open-Source-Lizenzen
Datenschutzrichtlinie
E-Klassenbuch
- © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - 2020
+ © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Februar 2021
Klicken Sie hier, um nach Aktualisierungen zu suchen
Aktualisierung
Version
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index f4a89910..ab80b93c 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -856,7 +856,7 @@
Open-source licenses
Privacy policy
E-register
- © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - 2020
+ © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - February 2021
Click to check for updates
Update
Version
diff --git a/app/src/main/res/values/errors.xml b/app/src/main/res/values/errors.xml
index 3e5f48d6..91fae735 100644
--- a/app/src/main/res/values/errors.xml
+++ b/app/src/main/res/values/errors.xml
@@ -130,6 +130,7 @@
ERROR_VULCAN_API_BAD_REQUEST
ERROR_VULCAN_API_OTHER
ERROR_VULCAN_ATTACHMENT_DOWNLOAD
+ ERROR_VULCAN_API_DEPRECATED
ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN
ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME
@@ -318,6 +319,7 @@
VULCAN®: błąd żądania, zgłoś błąd
VULCAN®: inny błąd, wyślij zgłoszenie
VULCAN®: nie znaleziono adresu załącznika
+ W związku z wygaszeniem aplikacji Dzienniczek+ przez firmę Vulcan, należy zalogować się ponownie.\n\nAby móc dalej korzystać z aplikacji Szkolny.eu, otwórz Ustawienia i wybierz opcję Dodaj nowego ucznia.\nNastępnie zaloguj się do dziennika Vulcan zgodnie z instrukcją.\n\nPrzepraszamy za niedogodności.
Nieprawidłowe dane logowania
Nieprawidłowa nazwa szkoły
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 40f79aa9..a913914d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -919,7 +919,7 @@
Licencje open-source
Polityka prywatności
E-dziennik
- © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - 2020
+ © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - luty 2021
Kliknij, aby sprawdzić aktualizacje
Aktualizacja
Wersja
diff --git a/build.gradle b/build.gradle
index 3b24a9a9..54a6a31b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.4.30'
release = [
- versionName: "4.4.3",
- versionCode: 4040399
+ versionName: "4.5-beta.2",
+ versionCode: 4050002
]
setup = [