Compare commits

...

28 Commits

Author SHA1 Message Date
359432d24d [4.6-beta.1] Update build.gradle, signing and changelog. 2021-02-25 19:54:19 +01:00
edf8ec20f0 [Proguard] Add rules for FSLogin. 2021-02-25 19:53:41 +01:00
3ad9e5da1f [Vulcan/Web] Update web login to work with FSLogin Realms. 2021-02-25 19:29:06 +01:00
459bbf78b2 [Vulcan/Hebe] Add getting attachments in homework. 2021-02-24 23:45:27 +01:00
d0baf02750 [Vulcan/Hebe] Remove login method dependency. 2021-02-24 22:09:51 +01:00
a5bb7d9c6e [Lab] Add option to full sync and clear profile. 2021-02-24 21:53:59 +01:00
a939d95ea3 [Vulcan/Hebe] Add getting attachments in messages. 2021-02-24 21:18:35 +01:00
4c081c970e [Vulcan/Hebe] Exclude more endpoints from first semester sync. 2021-02-23 12:50:37 +01:00
b75ab76c2a [Vulcan/Web] Fix checking login expiry time. 2021-02-23 12:48:17 +01:00
9da6dbccb3 [Vulcan/Web] Fix registering mobile device. 2021-02-23 12:47:52 +01:00
66444ae35b [Vulcan/Web] Fix extracting permissions string. 2021-02-23 12:47:20 +01:00
3e8b3de2b7 [Vulcan/Web] Move web data to loginStore per-symbol. 2021-02-23 12:00:01 +01:00
85ac5769a1 [4.5.1-beta.1] Update build.gradle, signing and changelog. 2021-02-22 23:25:44 +01:00
93e3d5994a [Vulcan/Hebe] Restore sending date range when getting attendance. 2021-02-22 23:20:32 +01:00
0cf24c527b [Vulcan/Hebe] Fix logging tag in notices class. 2021-02-22 23:04:10 +01:00
97c5acd6ba [Vulcan/Hebe] Fix checking for current year. 2021-02-22 22:59:02 +01:00
30aeb70647 [Vulcan/Hebe] Add filtering data by current school year. 2021-02-22 22:54:13 +01:00
b599d679c4 [Vulcan/Hebe] Implement sending messages. 2021-02-22 22:43:36 +01:00
1eecd24d91 [Vulcan/Hebe] Add getting notices. 2021-02-22 22:40:28 +01:00
c698dfdb73 [Vulcan/Hebe] Add getting grade summary. 2021-02-22 21:33:42 +01:00
c7a44f5ced [Vulcan/Hebe] Implement push notifications. 2021-02-22 19:13:25 +01:00
c8ee6ff1e7 [Vulcan/Hebe] Add getting lucky number. 2021-02-22 18:49:53 +01:00
552acd4043 [Vulcan/Web] Fix web login. 2021-02-22 18:23:27 +01:00
ede101ea20 [Vulcan/Hebe] Add saving lesson topic in attendance. 2021-02-22 17:51:52 +01:00
13c2640ed5 [UI] Hide API deprecation message on other profiles. 2021-02-22 17:43:34 +01:00
dd0739fd4b Merge branch 'develop' into feature/vulcan-hebe 2021-02-22 17:36:48 +01:00
9023f13932 [Vulcan/Hebe] Add missing copyright to files. 2021-02-22 17:18:40 +01:00
f8456fb087 [Lab] Add archiver enabled checkbox. 2021-02-22 16:37:53 +01:00
50 changed files with 798 additions and 176 deletions

View File

@ -211,7 +211,7 @@ dependencies {
implementation 'com.qifan.powerpermission:powerpermission:1.3.0' implementation 'com.qifan.powerpermission:powerpermission:1.3.0'
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0' implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0'
implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT' implementation 'com.github.kuba2k2.FSLogin:lib:2.0.0'
implementation 'pl.droidsonroids:jspoon:1.3.2' implementation 'pl.droidsonroids:jspoon:1.3.2'
implementation "com.squareup.retrofit2:converter-scalars:2.8.1" implementation "com.squareup.retrofit2:converter-scalars:2.8.1"
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"

View File

@ -67,3 +67,6 @@
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; } -keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; } -keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo$Platform { *; } -keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo$Platform { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData$Type { *; }

View File

@ -1,7 +1,8 @@
<h3>Wersja 4.5, 2021-02-21</h3> <h3>Wersja 4.6-beta.1, 2021-02-25</h3>
<ul> <ul>
<li>Vulcan: aplikacja Szkolny.eu zaktualizowana w związku z wygaszeniem aplikacji Dzienniczek+.</li> <li>Vulcan: dodano możliwość logowania adresem e-mail lub nazwą użytkownika.</li>
<li><b>Mogą pojawić się brakujące funkcje, np. wysyłanie wiadomości - zostaną one wprowadzone w najbliższych dniach.</b></li> <li>Vulcan: dodano obsługę załączników w wiadomościach i zadaniach domowych.</li>
<li>Zalecane jest zalogowanie się ponownie.</li>
</ul> </ul>
<br> <br>
<br> <br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0x4f, 0x43, 0x04, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0xe5, 0x07, 0x0f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -43,10 +43,9 @@ import androidx.viewpager.widget.ViewPager
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.*
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -538,6 +537,12 @@ fun String.md5(): String {
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0') return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
} }
fun String.sha1Hex(): String {
val md = MessageDigest.getInstance("SHA-1")
md.update(toByteArray())
return md.digest().joinToString("") { "%02x".format(it) }
}
fun String.sha256(): ByteArray { fun String.sha256(): ByteArray {
val md = MessageDigest.getInstance("SHA-256") val md = MessageDigest.getInstance("SHA-256")
md.update(toByteArray()) md.update(toByteArray())
@ -697,6 +702,21 @@ fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
} }
} }
fun JsonObject.toBundle(): Bundle {
return Bundle().also {
for ((key, value) in this.entrySet()) {
when (value) {
is JsonObject -> it.putBundle(key, value.toBundle())
is JsonPrimitive -> when {
value.isString -> it.putString(key, value.asString)
value.isBoolean -> it.putBoolean(key, value.asBoolean)
value.isNumber -> it.putInt(key, value.asInt)
}
}
}
}
}
fun JsonArray(vararg properties: Any?): JsonArray { fun JsonArray(vararg properties: Any?): JsonArray {
return JsonArray().apply { return JsonArray().apply {
for (property in properties) { for (property in properties) {

View File

@ -751,6 +751,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
EventBus.getDefault().removeStickyEvent(event) EventBus.getDefault().removeStickyEvent(event)
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
if (event.error.profileId != App.profileId)
return
ErrorDetailsDialog(this, listOf(event.error))
}
navView.toolbar.apply { navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
@ -758,9 +763,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
} }
mainSnackbar.dismiss() mainSnackbar.dismiss()
errorSnackbar.addError(event.error).show() errorSnackbar.addError(event.error).show()
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
ErrorDetailsDialog(this, listOf(event.error))
}
} }
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {

View File

@ -105,6 +105,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() } get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
set(value) { set("widgetConfigs", value); mWidgetConfigs = value } set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
private var mArchiverEnabled: Boolean? = null
var archiverEnabled: Boolean
get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true }
set(value) { set("archiverEnabled", value); mArchiverEnabled = value }
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow() private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf() private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
init { init {

View File

@ -127,15 +127,20 @@ const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get" 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_REGISTER_NEW = "api/mobile/register/new"
const val VULCAN_HEBE_ENDPOINT_MAIN = "api/mobile/register/hebe" const val VULCAN_HEBE_ENDPOINT_MAIN = "api/mobile/register/hebe"
const val VULCAN_HEBE_ENDPOINT_PUSH_ALL = "api/mobile/push/all"
const val VULCAN_HEBE_ENDPOINT_TIMETABLE = "api/mobile/schedule" const val VULCAN_HEBE_ENDPOINT_TIMETABLE = "api/mobile/schedule"
const val VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES = "api/mobile/schedule/changes" 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_ADDRESSBOOK = "api/mobile/addressbook"
const val VULCAN_HEBE_ENDPOINT_EXAMS = "api/mobile/exam" const val VULCAN_HEBE_ENDPOINT_EXAMS = "api/mobile/exam"
const val VULCAN_HEBE_ENDPOINT_GRADES = "api/mobile/grade" const val VULCAN_HEBE_ENDPOINT_GRADES = "api/mobile/grade"
const val VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY = "api/mobile/grade/summary"
const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework" const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework"
const val VULCAN_HEBE_ENDPOINT_NOTICES = "api/mobile/note"
const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson" const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson"
const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message" const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status" const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_SEND = "api/mobile/message"
const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky"
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}" const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -121,19 +121,15 @@ val vulcanLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java) LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
.withIsPossible { _, loginStore -> .withIsPossible { _, loginStore ->
loginStore.mode != LOGIN_MODE_VULCAN_HEBE loginStore.mode == LOGIN_MODE_VULCAN_API
} }
.withRequiredLoginMethod { _, loginStore -> .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
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) LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
.withIsPossible { _, loginStore -> .withIsPossible { _, loginStore ->
loginStore.mode != LOGIN_MODE_VULCAN_API loginStore.mode != LOGIN_MODE_VULCAN_API
} }
.withRequiredLoginMethod { _, loginStore -> .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
}
) )
val idziennikLoginMethods = listOf( val idziennikLoginMethods = listOf(

View File

@ -156,7 +156,7 @@ object Regexes {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
} }
val VULCAN_WEB_PERMISSIONS by lazy { val VULCAN_WEB_PERMISSIONS by lazy {
"""permissions: '([A-z0-9/=+\-_]+?)'""".toRegex() """permissions: '([A-z0-9/=+\-_|]+?)'""".toRegex()
} }
val VULCAN_WEB_SYMBOL_VALIDATE by lazy { val VULCAN_WEB_SYMBOL_VALIDATE by lazy {
"""[A-z0-9]+""".toRegex(IGNORE_CASE) """[A-z0-9]+""".toRegex(IGNORE_CASE)

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API 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_HEBE
@ -14,14 +15,13 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Team import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.fslogin.realm.RealmData
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isWebMainLoginValid() = symbol.isNotNullNorEmpty()
fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix() && (webExpiryTime[symbol]?.toLongOrNull() ?: 0) - 30 > currentTimeUnix()
&& webAuthCookie.isNotNullNorEmpty() && webAuthCookie[symbol].isNotNullNorEmpty()
&& webHost.isNotNullNorEmpty() && webRealmData != null
&& webType.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty()
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix() fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
&& apiFingerprint[symbol].isNotNullNorEmpty() && apiFingerprint[symbol].isNotNullNorEmpty()
&& apiPrivateKey[symbol].isNotNullNorEmpty() && apiPrivateKey[symbol].isNotNullNorEmpty()
@ -297,49 +297,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
\/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_| \/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_|
__/ | __/ |
|__*/ |__*/
/** var webRealmData: RealmData?
* Federation Services login type. get() { mWebRealmData = mWebRealmData ?: loginStore.getLoginData("webRealmData", JsonObject()).let {
* This might be one of: cufs, adfs, adfslight. app.gson.fromJson(it, RealmData::class.java)
*/ }; return mWebRealmData }
var webType: String? set(value) { loginStore.putLoginData("webRealmData", app.gson.toJsonTree(value) as JsonObject); mWebRealmData = value }
get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType } private var mWebRealmData: RealmData? = null
set(value) { loginStore.putLoginData("webType", value); mWebType = value }
private var mWebType: String? = null
/** val webHost
* Web server providing the federation services login. get() = webRealmData?.host
* If this is present, WEB_MAIN login is considered as available.
*/
var webHost: String?
get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost }
set(value) { loginStore.putLoginData("webHost", value); mWebHost = value }
private var mWebHost: String? = null
/**
* An ID used in ADFS & ADFSLight login types.
*/
var webAdfsId: String?
get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId }
set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value }
private var mWebAdfsId: String? = null
/**
* A domain override for ADFS Light.
*/
var webAdfsDomain: String?
get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain }
set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value }
private var mWebAdfsDomain: String? = null
var webIsHttpCufs: Boolean
get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false }
set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value }
private var mWebIsHttpCufs: Boolean? = null
var webIsScopedAdfs: Boolean
get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false }
set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value }
private var mWebIsScopedAdfs: Boolean? = null
var webEmail: String? var webEmail: String?
get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail } get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail }
@ -359,24 +325,24 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
* If the time passes, the certificate needs to be POSTed again (if valid) * If the time passes, the certificate needs to be POSTed again (if valid)
* or re-generated. * or re-generated.
*/ */
var webExpiryTime: Long var webExpiryTime: Map<String, String?> = mapOf()
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L } get() { mWebExpiryTime = mWebExpiryTime ?: loginStore.getLoginData("webExpiryTime", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebExpiryTime ?: mapOf() }
set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value } set(value) { loginStore.putLoginData("webExpiryTime", app.gson.toJson(value)); mWebExpiryTime = value }
private var mWebExpiryTime: Long? = null private var mWebExpiryTime: Map<String, String?>? = null
/** /**
* EfebSsoAuthCookie retrieved after posting a certificate * EfebSsoAuthCookie retrieved after posting a certificate
*/ */
var webAuthCookie: String? var webAuthCookie: Map<String, String?> = mapOf()
get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie } get() { mWebAuthCookie = mWebAuthCookie ?: loginStore.getLoginData("webAuthCookie", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebAuthCookie ?: mapOf() }
set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value } set(value) { loginStore.putLoginData("webAuthCookie", app.gson.toJson(value)); mWebAuthCookie = value }
private var mWebAuthCookie: String? = null private var mWebAuthCookie: Map<String, String?>? = null
/** /**
* Permissions needed to get JSONs from home page * Permissions needed to get JSONs from home page
*/ */
var webPermissions: String? var webPermissions: Map<String, String?> = mapOf()
get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions } get() { mWebPermissions = mWebPermissions ?: loginStore.getLoginData("webPermissions", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebPermissions ?: mapOf() }
set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value } set(value) { loginStore.putLoginData("webPermissions", app.gson.toJson(value)); mWebPermissions = value }
private var mWebPermissions: String? = null private var mWebPermissions: Map<String, String?>? = null
} }

View File

@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiAt
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiMessagesChangeStatus 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.api.VulcanApiSendMessage
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMessagesChangeStatus import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMessagesChangeStatus
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeSendMessage
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin.VulcanFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin.VulcanFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
@ -135,6 +136,15 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun sendMessage(recipients: List<Teacher>, subject: String, text: String) { override fun sendMessage(recipients: List<Teacher>, subject: String, text: String) {
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
login(LOGIN_METHOD_VULCAN_HEBE) {
VulcanHebeSendMessage(data, recipients, subject, text) {
completed()
}
}
return
}
login(LOGIN_METHOD_VULCAN_API) { login(LOGIN_METHOD_VULCAN_API) {
VulcanApiSendMessage(data, recipients, subject, text) { VulcanApiSendMessage(data, recipients, subject, text) {
completed() completed()
@ -190,6 +200,14 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun getEvent(eventFull: EventFull) { override fun getEvent(eventFull: EventFull) {
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
eventFull.homeworkBody = ""
EventBus.getDefault().postSticky(EventGetEvent(eventFull))
completed()
return
}
login(LOGIN_METHOD_VULCAN_API) { login(LOGIN_METHOD_VULCAN_API) {
val list = data.app.db.eventDao().getAllNow(data.profileId).filter { !it.addedManually } val list = data.app.db.eventDao().getAllNow(data.profileId).filter { !it.addedManually }
VulcanApiAttachments(data, list, eventFull, EventFull::class) { _ -> VulcanApiAttachments(data, list, eventFull, EventFull::class) { _ ->

View File

@ -19,16 +19,22 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070
const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080 const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080
const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090 const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090
const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100 const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100
const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010 const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
const val ENDPOINT_VULCAN_HEBE_MAIN = 3000 const val ENDPOINT_VULCAN_HEBE_MAIN = 3000
const val ENDPOINT_VULCAN_HEBE_PUSH_CONFIG = 3005
const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010 const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020 const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030 const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040 const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
const val ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY = 3050
const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060 const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060
const val ENDPOINT_VULCAN_HEBE_NOTICES = 3070
const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080 const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090 const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100 const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100
const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
val VulcanFeatures = listOf( val VulcanFeatures = listOf(
// timetable // timetable
@ -51,7 +57,8 @@ val VulcanFeatures = listOf(
ENDPOINT_VULCAN_API_GRADES_SUMMARY to LOGIN_METHOD_VULCAN_API ENDPOINT_VULCAN_API_GRADES_SUMMARY to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)), ), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf(
ENDPOINT_VULCAN_HEBE_GRADES to LOGIN_METHOD_VULCAN_HEBE ENDPOINT_VULCAN_HEBE_GRADES to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)), ), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// homework // homework
Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf(
@ -64,6 +71,9 @@ val VulcanFeatures = listOf(
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_VULCAN_API_NOTICES to LOGIN_METHOD_VULCAN_API ENDPOINT_VULCAN_API_NOTICES to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)), ), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_VULCAN_HEBE_NOTICES to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// attendance // attendance
Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf(
ENDPOINT_VULCAN_API_ATTENDANCE to LOGIN_METHOD_VULCAN_API ENDPOINT_VULCAN_API_ATTENDANCE to LOGIN_METHOD_VULCAN_API
@ -91,13 +101,28 @@ val VulcanFeatures = listOf(
), listOf(LOGIN_METHOD_VULCAN_API)).withShouldSync { data -> ), listOf(LOGIN_METHOD_VULCAN_API)).withShouldSync { data ->
!data.app.config.sync.tokenVulcanList.contains(data.profileId) !data.app.config.sync.tokenVulcanList.contains(data.profileId)
}, },
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)).withShouldSync { data ->
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
},
/** /**
* Lucky number - using WEB Main. * Lucky number - using WEB Main.
*/ */
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() }, ), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN))
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
.withPriority(2),
/**
* Lucky number - using Hebe API
*/
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE))
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
.withPriority(1),
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API, ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API,

View File

@ -22,10 +22,15 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
private var firstSemesterSync = false private var firstSemesterSync = false
private val firstSemesterSyncExclude = listOf( private val firstSemesterSyncExclude = listOf(
ENDPOINT_VULCAN_HEBE_MAIN, ENDPOINT_VULCAN_HEBE_MAIN,
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
ENDPOINT_VULCAN_HEBE_TIMETABLE, ENDPOINT_VULCAN_HEBE_TIMETABLE,
ENDPOINT_VULCAN_HEBE_EXAMS,
ENDPOINT_VULCAN_HEBE_HOMEWORK,
ENDPOINT_VULCAN_HEBE_NOTICES,
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX, ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
) )
init { run { init { run {
@ -148,6 +153,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
onSuccess(ENDPOINT_VULCAN_HEBE_MAIN) onSuccess(ENDPOINT_VULCAN_HEBE_MAIN)
} }
} }
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG -> {
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
VulcanHebePushConfig(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK -> { ENDPOINT_VULCAN_HEBE_ADDRESSBOOK -> {
data.startProgress(R.string.edziennik_progress_endpoint_teachers) data.startProgress(R.string.edziennik_progress_endpoint_teachers)
VulcanHebeAddressbook(data, lastSync, onSuccess) VulcanHebeAddressbook(data, lastSync, onSuccess)
@ -164,10 +173,22 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)
VulcanHebeGrades(data, lastSync, onSuccess) VulcanHebeGrades(data, lastSync, onSuccess)
} }
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY -> {
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
VulcanHebeGradeSummary(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_HOMEWORK -> { ENDPOINT_VULCAN_HEBE_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework) data.startProgress(R.string.edziennik_progress_endpoint_homework)
VulcanHebeHomework(data, lastSync, onSuccess) VulcanHebeHomework(data, lastSync, onSuccess)
} }
ENDPOINT_VULCAN_HEBE_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
VulcanHebeNotices(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanHebeAttendance(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> { ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED) VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED)
@ -176,9 +197,9 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_SENT) VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_SENT)
} }
ENDPOINT_VULCAN_HEBE_ATTENDANCE -> { ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance) data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
VulcanHebeAttendance(data, lastSync, onSuccess) VulcanHebeLuckyNumber(data, lastSync, onSuccess)
} }
else -> onSuccess(endpointId) else -> onSuccess(endpointId)
} }

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
import android.os.Build import android.os.Build
@ -42,7 +46,11 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
val profile val profile
get() = data.profile get() = data.profile
fun getDateTime(json: JsonObject?, key: String, default: Long = System.currentTimeMillis()): Long { fun getDateTime(
json: JsonObject?,
key: String,
default: Long = System.currentTimeMillis()
): Long {
val date = json.getJsonObject(key) val date = json.getJsonObject(key)
return date.getLong("Timestamp") ?: return default return date.getLong("Timestamp") ?: return default
} }
@ -142,6 +150,18 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
2 2
} }
fun isCurrentYear(date: Date): Boolean {
return profile?.let { profile ->
return@let date >= profile.dateSemester1Start
} ?: false
}
fun isCurrentYear(dateTime: Long): Boolean {
return profile?.let { profile ->
return@let dateTime >= profile.dateSemester1Start.inMillis
} ?: false
}
inline fun <reified T> apiRequest( inline fun <reified T> apiRequest(
tag: String, tag: String,
endpoint: String, endpoint: String,
@ -193,7 +213,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) { if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(
ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response) .withResponse(response)
) )
return return
@ -201,19 +222,24 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
val status = json.getJsonObject("Status") val status = json.getJsonObject("Status")
if (status?.getInt("Code") != 0) { if (status?.getInt("Code") != 0) {
data.error(ApiError(tag, ERROR_VULCAN_HEBE_OTHER) data.error(
.withResponse(response) ApiError(tag, ERROR_VULCAN_HEBE_OTHER)
.withApiResponse(json.toString())) .withResponse(response)
.withApiResponse(json.toString())
)
} }
val envelope = when (T::class.java) { val envelope = if (json.get("Envelope").isJsonNull && null is T)
null as T
else when (T::class.java) {
JsonObject::class.java -> json.getJsonObject("Envelope") as T JsonObject::class.java -> json.getJsonObject("Envelope") as T
JsonArray::class.java -> json.getJsonArray("Envelope") as T JsonArray::class.java -> json.getJsonArray("Envelope") as T
java.lang.Boolean::class.java -> json.getBoolean("Envelope") as T java.lang.Boolean::class.java -> json.getBoolean("Envelope") as T
else -> { else -> {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) data.error(
.withResponse(response) ApiError(tag, ERROR_RESPONSE_EMPTY)
.withApiResponse(json) .withResponse(response)
.withApiResponse(json)
) )
return return
} }
@ -222,7 +248,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
try { try {
onSuccess(envelope, response) onSuccess(envelope, response)
} catch (e: Exception) { } catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST) data.error(
ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST)
.withResponse(response) .withResponse(response)
.withThrowable(e) .withThrowable(e)
.withApiResponse(json) .withApiResponse(json)
@ -231,7 +258,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
} }
override fun onFailure(response: Response?, throwable: Throwable?) { override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE) data.error(
ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response) .withResponse(response)
.withThrowable(throwable) .withThrowable(throwable)
) )
@ -347,10 +375,15 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
if (folder != null) if (folder != null)
query["folder"] = folder.toString() query["folder"] = folder.toString()
val semester1Start = profile?.dateSemester1Start?.inMillis
query["lastId"] = "-2147483648" // don't ask, it's just Vulcan query["lastId"] = "-2147483648" // don't ask, it's just Vulcan
query["pageSize"] = "500" query["pageSize"] = "500"
query["lastSyncDate"] = LocalDateTime query["lastSyncDate"] = LocalDateTime
.ofInstant(Instant.ofEpochMilli(lastSync ?: 0), ZoneId.systemDefault()) .ofInstant(
Instant.ofEpochMilli(lastSync ?: semester1Start ?: 0),
ZoneId.systemDefault()
)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
apiGet(tag, url, query) { json: JsonArray, response -> apiGet(tag, url, query) { json: JsonArray, response ->

View File

@ -11,12 +11,11 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.droidsonroids.jspoon.Jspoon import pl.droidsonroids.jspoon.Jspoon
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.isNotNullNorBlank
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.io.File import java.io.File
@ -83,10 +82,12 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
onResult(symbol, STATE_NO_REGISTER) onResult(symbol, STATE_NO_REGISTER)
return return
} }
if (!validateCallback(text, response, jsonResponse = false)) { if (!validateCallback(symbol, text, response, jsonResponse = false)) {
return return
} }
data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L data.webExpiryTime = data.webExpiryTime.toMutableMap().also { map ->
map[symbol] = (Date.fromIso(certificate.expiryDate) / 1000L).toString()
}
onResult(symbol, STATE_SUCCESS) onResult(symbol, STATE_SUCCESS)
} }
@ -120,7 +121,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List<String>) -> Unit) { fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List<String>) -> Unit) {
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) { override fun onSuccess(text: String?, response: Response?) {
if (!validateCallback(text, response, jsonResponse = false) || text == null) { if (!validateCallback(symbol, text, response, jsonResponse = false) || text == null) {
return return
} }
@ -136,7 +137,30 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
} }
} }
data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] } data.webPermissions = data.webPermissions.toMutableMap().also { map ->
val permissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
if (permissions?.isNotBlank() == true) {
val studentId = permissions.split("|")
.getOrNull(0)
?.base64DecodeToString()
?.toJsonObject()
?.getJsonArray("AuthInfos")
?.asJsonObjectList()
?.flatMap { authInfo ->
authInfo.getJsonArray("UczenIds")
?.map { it.asInt }
?: listOf()
}
?.firstOrNull()
?.toString()
data.app.cookieJar.set(
data.webHost ?: "vulcan.net.pl",
"idBiezacyUczen",
studentId
)
}
map[symbol] = permissions
}
val schoolSymbols = mutableListOf<String>() val schoolSymbols = mutableListOf<String>()
val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/" val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/"
@ -144,7 +168,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
var count = 0 var count = 0
while (clientIndex != -1 && count < 100) { while (clientIndex != -1 && count < 100) {
val startIndex = clientIndex + clientUrl.length val startIndex = clientIndex + clientUrl.length
val endIndex = text.indexOf('/', startIndex = startIndex) val endIndex = text.indexOf('"', startIndex = startIndex)
val schoolSymbol = text.substring(startIndex, endIndex) val schoolSymbol = text.substring(startIndex, endIndex)
schoolSymbols += schoolSymbol schoolSymbols += schoolSymbol
clientIndex = text.indexOf(clientUrl, startIndex = endIndex) clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
@ -186,7 +210,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
.enqueue() .enqueue()
} }
private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean { private fun validateCallback(symbol: String, text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
if (text == null) { if (text == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -207,11 +231,13 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl") val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl")
val authCookie = cookies["EfebSsoAuthCookie"] val authCookie = cookies["EfebSsoAuthCookie"]
if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) { if ((authCookie == null || authCookie == "null") && data.webAuthCookie[symbol] != null) {
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie) data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie[symbol])
} }
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) { else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie[symbol]) {
data.webAuthCookie = authCookie data.webAuthCookie = data.webAuthCookie.toMutableMap().also { map ->
map[symbol] = authCookie
}
} }
return true return true
} }
@ -250,7 +276,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) { override fun onSuccess(text: String?, response: Response?) {
if (!validateCallback(text, response)) if (!validateCallback(data.symbol ?: "default", text, response))
return return
try { try {

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
enum class HebeFilterType(val endpoint: String) { enum class HebeFilterType(val endpoint: String) {

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set import androidx.core.util.set

View File

@ -85,6 +85,7 @@ class VulcanHebeAttendance(
subjectId = subjectId, subjectId = subjectId,
addedDate = addedDate addedDate = addedDate
).also { ).also {
it.lessonTopic = attendance.getString("Topic")
it.lessonNumber = lessonNumber it.lessonNumber = lessonNumber
it.isCounted = isCounted it.isCounted = isCounted
} }

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
@ -36,6 +40,8 @@ class VulcanHebeExams(
?: -1 ?: -1
val topic = exam.getString("Content")?.trim() ?: "" val topic = exam.getString("Content")?.trim() ?: ""
if (!isCurrentYear(eventDate)) return@forEach
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
class VulcanHebeGradeSummary(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeGradeSummary"
}
init {
val entries = mapOf(
"Entry_1" to
if (data.studentSemesterNumber == 1)
Grade.TYPE_SEMESTER1_PROPOSED
else Grade.TYPE_SEMESTER2_PROPOSED,
"Entry_2" to
if (data.studentSemesterNumber == 1)
Grade.TYPE_SEMESTER1_FINAL
else Grade.TYPE_SEMESTER2_FINAL
)
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY,
HebeFilterType.BY_PUPIL,
lastSync = lastSync
) { list, _ ->
list.forEach { grade ->
val subjectId = getSubjectId(grade, "Subject") ?: return@forEach
val addedDate = getDateTime(grade, "DateModify")
entries.onEach { (key, type) ->
val id = subjectId * -100 - type
val entry = grade.getString(key) ?: return@onEach
val value = Utils.getGradeValue(entry)
val color = Utils.getVulcanGradeColor(entry)
val gradeObject = Grade(
profileId = profileId,
id = id,
name = entry,
type = type,
value = value,
weight = 0f,
color = color,
category = "",
description = null,
comment = null,
semester = data.studentSemesterNumber,
teacherId = -1,
subjectId = subjectId,
addedDate = addedDate
)
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_GRADE,
id,
profile?.empty ?: true,
profile?.empty ?: true
)
)
}
}
data.setSyncNext(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY, 1 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY)
}
}
}

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*

View File

@ -1,5 +1,10 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_HOMEWORK import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan 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.ENDPOINT_VULCAN_HEBE_HOMEWORK
@ -7,8 +12,10 @@ 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.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
class VulcanHebeHomework( class VulcanHebeHomework(
override val data: DataVulcan, override val data: DataVulcan,
@ -34,6 +41,8 @@ class VulcanHebeHomework(
val teamId = data.teamClass?.id ?: -1 val teamId = data.teamClass?.id ?: -1
val topic = exam.getString("Content")?.trim() ?: "" val topic = exam.getString("Content")?.trim() ?: ""
if (!isCurrentYear(eventDate)) return@forEach
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
@ -50,6 +59,22 @@ class VulcanHebeHomework(
teamId = teamId teamId = teamId
) )
val attachments = exam.getJsonArray("Attachments")
?.asJsonObjectList()
?: return@forEach
for (attachment in attachments) {
val fileName = attachment.getString("Name") ?: continue
val url = attachment.getString("Link") ?: continue
val attachmentName = "$fileName:$url"
val attachmentId = Utils.crc32(attachmentName.toByteArray())
eventObject.addAttachment(
id = attachmentId,
name = attachmentName
)
}
data.eventList.add(eventObject) data.eventList.add(eventObject)
data.metadataList.add( data.metadataList.add(
Metadata( Metadata(

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
class VulcanHebeLuckyNumber(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeLuckyNumber"
}
init {
apiGet(
TAG,
VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER,
query = mapOf(
"constituentId" to data.studentConstituentId.toString(),
"day" to Date.getToday().stringY_m_d
)
) { lucky: JsonObject?, _ ->
// sync tomorrow if lucky number set or is weekend or afternoon
var nextSync = Date.getToday().stepForward(0, 0, 1).inMillis
if (lucky == null) {
if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 12) {
// working days morning, sync always
nextSync = SYNC_ALWAYS
}
}
else {
val luckyNumberDate = Date.fromY_m_d(lucky.getString("Day")) ?: Date.getToday()
val luckyNumber = lucky.getInt("Number") ?: -1
val luckyNumberObject = LuckyNumber(
profileId = profileId,
date = luckyNumberDate,
number = luckyNumber
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
true,
profile?.empty ?: false
)
)
}
data.setSyncNext(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER, syncAt = nextSync)
onSuccess(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER)
}
}
}

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonArray import com.google.gson.JsonArray

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set import androidx.core.util.set
@ -13,6 +17,7 @@ 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_DELETED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.navlib.crc16 import pl.szczodrzynski.navlib.crc16
import kotlin.text.replace import kotlin.text.replace
@ -76,6 +81,8 @@ class VulcanHebeMessages(
val sentDate = getDateTime(message, "DateSent") val sentDate = getDateTime(message, "DateSent")
val readDate = getDateTime(message, "DateRead", default = 0) val readDate = getDateTime(message, "DateRead", default = 0)
if (!isCurrentYear(sentDate)) return@forEach
val messageObject = Message( val messageObject = Message(
profileId = profileId, profileId = profileId,
id = id, id = id,
@ -104,6 +111,23 @@ class VulcanHebeMessages(
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
} }
val attachments = message.getJsonArray("Attachments")
?.asJsonObjectList()
?: return@forEach
for (attachment in attachments) {
val fileName = attachment.getString("Name") ?: continue
val url = attachment.getString("Link") ?: continue
val attachmentName = "$fileName:$url"
val attachmentId = Utils.crc32(attachmentName.toByteArray())
messageObject.addAttachment(
id = attachmentId,
name = attachmentName,
size = -1
)
}
data.messageList.add(messageObject) data.messageList.add(messageObject)
data.setSeenMetadataList.add( data.setSeenMetadataList.add(
Metadata( Metadata(

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2021-2-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
class VulcanHebeNotices(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeNotices"
}
init {
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_NOTICES,
HebeFilterType.BY_PUPIL,
lastSync = lastSync
) { list, _ ->
list.forEach { notice ->
val id = notice.getLong("Id") ?: return@forEach
val type = when (notice.getBoolean("Positive")) {
true -> Notice.TYPE_POSITIVE
else -> Notice.TYPE_NEUTRAL
}
val date = getDate(notice, "DateValid") ?: return@forEach
val semester = profile?.dateToSemester(date) ?: return@forEach
val text = notice.getString("Content") ?: ""
val category = notice.getJsonObject("Category")?.getString("Name")
val points = notice.getFloat("Points")
val teacherId = getTeacherId(notice, "Creator") ?: -1
val addedDate = getDateTime(notice, "DateModify")
if (!isCurrentYear(date)) return@forEach
val noticeObject = Notice(
profileId = profileId,
id = id,
type = type,
semester = semester,
text = text,
category = category,
points = points,
teacherId = teacherId,
addedDate = addedDate
)
data.noticeList.add(noticeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_NOTICE,
id,
profile?.empty ?: true,
profile?.empty ?: true
)
)
}
data.setSyncNext(ENDPOINT_VULCAN_HEBE_NOTICES, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_HEBE_NOTICES)
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonPrimitive
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_PUSH_ALL
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_PUSH_CONFIG
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
class VulcanHebePushConfig(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebePushConfig"
}
init {
apiPost(
TAG,
VULCAN_HEBE_ENDPOINT_PUSH_ALL,
payload = JsonPrimitive("on")
) { _: Boolean, _ ->
// sync always: this endpoint has .shouldSync set
data.setSyncNext(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG, SYNC_ALWAYS)
data.app.config.sync.tokenVulcanList =
data.app.config.sync.tokenVulcanList + profileId
onSuccess(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG)
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonObject
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
class VulcanHebeSendMessage(
override val data: DataVulcan,
val recipients: List<Teacher>,
val subject: String,
val text: String,
val onSuccess: () -> Unit
) : VulcanHebe(data, null) {
companion object {
const val TAG = "VulcanHebeSendMessage"
}
init {
val recipientsArray = JsonArray()
recipients.forEach { teacher ->
recipientsArray += JsonObject(
"Address" to teacher.fullNameLastFirst,
"LoginId" to (teacher.loginId?.toIntOrNull() ?: return@forEach),
"Initials" to teacher.initialsLastFirst,
"AddressHash" to teacher.fullNameLastFirst.sha1Hex()
)
}
val senderName = (profile?.accountName ?: profile?.studentNameLong)
?.swapFirstLastName() ?: ""
val sender = JsonObject(
"Address" to senderName,
"LoginId" to data.studentLoginId.toString(),
"Initials" to senderName.getNameInitials(),
"AddressHash" to senderName.sha1Hex()
)
apiPost(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_SEND,
payload = JsonObject(
"Status" to 1,
"Sender" to sender,
"DateSent" to null,
"DateRead" to null,
"Content" to text,
"Receiver" to recipientsArray,
"Id" to 0,
"Subject" to subject,
"Attachments" to null,
"Self" to null
)
) { json: JsonObject, _ ->
val messageId = json.getLong("Id")
if (messageId == null) {
// TODO error
return@apiPost
}
VulcanHebeMessages(data, null) {
val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject }
val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == messageId }
val event = MessageSentEvent(data.profileId, message, message?.addedDate)
EventBus.getDefault().postSticky(event)
onSuccess()
}.getMessages(Message.TYPE_SENT)
}
}
}

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonObject import com.google.gson.JsonObject

View File

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
import com.google.gson.JsonObject import com.google.gson.JsonObject

View File

@ -13,7 +13,7 @@ import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.fslogin.FSLogin import pl.szczodrzynski.fslogin.FSLogin
import pl.szczodrzynski.fslogin.realm.CufsRealm import pl.szczodrzynski.fslogin.realm.toRealm
class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) { class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
companion object { companion object {
@ -30,8 +30,7 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
} }
else { else {
if (data.symbol.isNotNullNorEmpty() if (data.symbol.isNotNullNorEmpty()
&& data.webType.isNotNullNorEmpty() && data.webRealmData != null
&& data.webHost.isNotNullNorEmpty()
&& (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty()) && (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty())
&& data.webPassword.isNotNullNorEmpty()) { && data.webPassword.isNotNullNorEmpty()) {
try { try {
@ -56,32 +55,28 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
data.symbol = getString("symbol") data.symbol = getString("symbol")
remove("symbol") remove("symbol")
} }
// 4.6 - form inputs renamed
if (has("email")) {
data.webEmail = getString("email")
remove("email")
}
if (has("username")) {
data.webUsername = getString("username")
remove("username")
}
if (has("password")) {
data.webPassword = getString("password")
remove("password")
}
}
if (data.symbol == null && data.webRealmData != null) {
data.symbol = data.webRealmData?.symbol
} }
} }
private fun loginWithCredentials(): Boolean { private fun loginWithCredentials(): Boolean {
val realm = when (data.webType) { val realm = data.webRealmData?.toRealm() ?: return false
"cufs" -> CufsRealm(
host = data.webHost ?: return false,
symbol = data.symbol ?: "default",
httpCufs = data.webIsHttpCufs
)
"adfs" -> CufsRealm(
host = data.webHost ?: return false,
symbol = data.symbol ?: "default",
httpCufs = data.webIsHttpCufs
).toAdfsRealm(id = data.webAdfsId ?: return false)
"adfslight" -> CufsRealm(
host = data.webHost ?: return false,
symbol = data.symbol ?: "default",
httpCufs = data.webIsHttpCufs
).toAdfsLightRealm(
id = data.webAdfsId ?: return false,
domain = data.webAdfsDomain ?: "adfslight",
isScoped = data.webIsScopedAdfs
)
else -> return false
}
val certificate = web.readCertificate()?.let { web.parseCertificate(it) } val certificate = web.readCertificate()?.let { web.parseCertificate(it) }
if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) { if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) {

View File

@ -348,8 +348,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getPlatforms(registerName: String): List<LoginInfo.Platform> { fun getRealms(registerName: String): List<LoginInfo.Platform> {
val response = api.appLoginPlatforms(registerName).execute() val response = api.fsLoginRealms(registerName).execute()
return parseResponse(response) return parseResponse(response)
} }

View File

@ -33,12 +33,12 @@ interface SzkolnyService {
@POST("feedbackMessage") @POST("feedbackMessage")
fun feedbackMessage(@Body request: FeedbackMessageRequest): Call<ApiResponse<FeedbackMessageResponse>> fun feedbackMessage(@Body request: FeedbackMessageRequest): Call<ApiResponse<FeedbackMessageResponse>>
@GET("appLogin/platforms/{registerName}")
fun appLoginPlatforms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
@GET("firebase/token/{registerName}") @GET("firebase/token/{registerName}")
fun firebaseToken(@Path("registerName") registerName: String): Call<ApiResponse<String>> fun firebaseToken(@Path("registerName") registerName: String): Call<ApiResponse<String>>
@GET("registerAvailability") @GET("registerAvailability")
fun registerAvailability(): Call<ApiResponse<Map<String, RegisterAvailabilityStatus>>> fun registerAvailability(): Call<ApiResponse<Map<String, RegisterAvailabilityStatus>>>
@GET("fsLogin/{registerName}")
fun fsLoginRealms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
} }

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/ /*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MD5LwH9bSc===.$param2".sha256() return "$param1.MTIzNDU2Nzg5MDAwogntL3===.$param2".sha256()
} }
} }

View File

@ -93,6 +93,25 @@ open class Event(
var attachmentIds: MutableList<Long>? = null var attachmentIds: MutableList<Long>? = null
var attachmentNames: MutableList<String>? = null var attachmentNames: MutableList<String>? = null
/**
* Add an attachment
* @param id attachment ID
* @param name file name incl. extension
* @return a Event to which the attachment has been added
*/
fun addAttachment(id: Long, name: String): Event {
if (attachmentIds == null) attachmentIds = mutableListOf()
if (attachmentNames == null) attachmentNames = mutableListOf()
attachmentIds?.add(id)
attachmentNames?.add(name)
return this
}
fun clearAttachments() {
attachmentIds = null
attachmentNames = null
}
@Ignore @Ignore
var showAsUnseen: Boolean? = null var showAsUnseen: Boolean? = null

View File

@ -59,6 +59,7 @@ class LoginStore(
is Long -> putLoginData(key, o) is Long -> putLoginData(key, o)
is Float -> putLoginData(key, o) is Float -> putLoginData(key, o)
is Boolean -> putLoginData(key, o) is Boolean -> putLoginData(key, o)
is Bundle -> putLoginData(key, o.toJsonObject())
} }
} }
} }

View File

@ -103,7 +103,9 @@ open class Profile(
dateSemester2Start.year -= diff dateSemester2Start.year -= diff
dateYearEnd.year -= diff dateYearEnd.year -= diff
} }
return Date.getToday() >= dateYearEnd && Date.getToday().year > studentSchoolYearStart return App.config.archiverEnabled
&& Date.getToday() >= dateYearEnd
&& Date.getToday().year > studentSchoolYearStart
} }
fun isBeforeYear() = false && Date.getToday() < dateSemester1Start fun isBeforeYear() = false && Date.getToday() < dateSemester1Start

View File

@ -11,6 +11,7 @@ import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.getNameInitials
import pl.szczodrzynski.edziennik.join import pl.szczodrzynski.edziennik.join
import java.util.* import java.util.*
@ -180,6 +181,9 @@ open class Teacher {
@delegate:Ignore @delegate:Ignore
val fullNameLastFirst by lazy { "$surname $name".fixName() } val fullNameLastFirst by lazy { "$surname $name".fixName() }
@delegate:Ignore
val initialsLastFirst by lazy { fullNameLastFirst.getNameInitials() }
val shortName: String val shortName: String
get() = (if (name == null || name?.length == 0) "" else name!![0].toString()) + "." + surname get() = (if (name == null || name?.length == 0) "" else name!![0].toString()) + "." + surname

View File

@ -29,6 +29,7 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
val data = message.data.getString("data")?.toJsonObject() ?: return@run val data = message.data.getString("data")?.toJsonObject() ?: return@run
val type = data.getString("table") ?: return@run val type = data.getString("table") ?: return@run
val studentId = data.getInt("pupilid") val studentId = data.getInt("pupilid")
val loginId = data.getInt("loginid")
/* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */ /* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */
val viewIdPair = when (type.toLowerCase(Locale.ROOT)) { val viewIdPair = when (type.toLowerCase(Locale.ROOT)) {
@ -42,8 +43,9 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
} }
val tasks = profiles.filter { val tasks = profiles.filter {
it.loginStoreType == LOGIN_TYPE_VULCAN && it.loginStoreType == LOGIN_TYPE_VULCAN
it.getStudentData("studentId", 0) == studentId && (it.getStudentData("studentId", 0) == studentId
|| it.getStudentData("studentLoginId", 0) == loginId)
}.map { }.map {
EdziennikTask.syncProfile(it.id, listOf(viewIdPair)) EdziennikTask.syncProfile(it.id, listOf(viewIdPair))
} }

View File

@ -17,7 +17,8 @@ import kotlin.coroutines.CoroutineContext
class ProfileRemoveDialog( class ProfileRemoveDialog(
val activity: MainActivity, val activity: MainActivity,
val profileId: Int, val profileId: Int,
val profileName: String val profileName: String,
val noProfileRemoval: Boolean = false
) : CoroutineScope { ) : CoroutineScope {
companion object { companion object {
private const val TAG = "ProfileRemoveDialog" private const val TAG = "ProfileRemoveDialog"
@ -52,7 +53,6 @@ class ProfileRemoveDialog(
app.db.attendanceDao().clear(profileId) app.db.attendanceDao().clear(profileId)
app.db.attendanceTypeDao().clear(profileId) app.db.attendanceTypeDao().clear(profileId)
app.db.classroomDao().clear(profileId) app.db.classroomDao().clear(profileId)
app.db.configDao().clear(profileId)
app.db.endpointTimerDao().clear(profileId) app.db.endpointTimerDao().clear(profileId)
app.db.eventDao().clear(profileId) app.db.eventDao().clear(profileId)
app.db.eventTypeDao().clear(profileId) app.db.eventTypeDao().clear(profileId)
@ -65,23 +65,27 @@ class ProfileRemoveDialog(
app.db.messageRecipientDao().clear(profileId) app.db.messageRecipientDao().clear(profileId)
app.db.noticeDao().clear(profileId) app.db.noticeDao().clear(profileId)
app.db.noticeTypeDao().clear(profileId) app.db.noticeTypeDao().clear(profileId)
app.db.noticeTypeDao().clear(profileId)
app.db.notificationDao().clear(profileId) app.db.notificationDao().clear(profileId)
app.db.subjectDao().clear(profileId) app.db.subjectDao().clear(profileId)
app.db.teacherAbsenceDao().clear(profileId) app.db.teacherAbsenceDao().clear(profileId)
app.db.teacherAbsenceDao().clear(profileId)
app.db.teacherAbsenceTypeDao().clear(profileId) app.db.teacherAbsenceTypeDao().clear(profileId)
app.db.teacherDao().clear(profileId) app.db.teacherDao().clear(profileId)
app.db.teamDao().clear(profileId) app.db.teamDao().clear(profileId)
app.db.timetableDao().clear(profileId) app.db.timetableDao().clear(profileId)
app.db.metadataDao().deleteAll(profileId)
if (noProfileRemoval)
return@async
app.db.configDao().clear(profileId)
val loginStoreId = profileObject.loginStoreId val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId) val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) { if (profilesUsingLoginStore.size == 1) {
app.db.loginStoreDao().remove(loginStoreId) app.db.loginStoreDao().remove(loginStoreId)
} }
app.db.profileDao().remove(profileId) app.db.profileDao().remove(profileId)
app.db.metadataDao().deleteAll(profileId)
if (App.profileId == profileId) { if (App.profileId == profileId) {
app.profileLoadLast { } app.profileLoadLast { }

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.fslogin.decode import pl.szczodrzynski.fslogin.decode
@ -45,6 +46,8 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
} }
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
b.app = app
b.last10unseen.onClick { b.last10unseen.onClick {
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
val events = app.db.eventDao().getAllNow(App.profileId) val events = app.db.eventDao().getAllNow(App.profileId)
@ -58,6 +61,15 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
b.rodo.onClick { b.rodo.onClick {
app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}")) app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}"))
} }
b.fullSync.onClick {
app.db.query(SimpleSQLiteQuery("UPDATE profiles SET empty = 1 WHERE profileId = ${App.profileId}"))
app.db.query(SimpleSQLiteQuery("DELETE FROM endpointTimers WHERE profileId = ${App.profileId}"))
}
b.clearProfile.onClick {
ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true)
}
b.removeHomework.onClick { b.removeHomework.onClick {
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")

View File

@ -14,7 +14,6 @@ import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.JsonParser
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.paddingDp import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
@ -72,7 +71,7 @@ class LoginFormFragment : Fragment(), CoroutineScope {
val platformGuideText = arguments?.getString("platformGuideText") val platformGuideText = arguments?.getString("platformGuideText")
val platformDescription = arguments?.getString("platformDescription") val platformDescription = arguments?.getString("platformDescription")
val platformFormFields = arguments?.getString("platformFormFields")?.split(";") val platformFormFields = arguments?.getString("platformFormFields")?.split(";")
val platformApiData = arguments?.getString("platformApiData")?.let { JsonParser().parse(it)?.asJsonObject } val platformRealmData = arguments?.getString("platformRealmData")?.toJsonObject()
b.title.setText(R.string.login_form_title_format, app.getString(register.registerName)) b.title.setText(R.string.login_form_title_format, app.getString(register.registerName))
b.subTitle.text = platformName ?: app.getString(mode.name) b.subTitle.text = platformName ?: app.getString(mode.name)
@ -159,9 +158,7 @@ class LoginFormFragment : Fragment(), CoroutineScope {
payload.putBoolean("fakeLogin", true) payload.putBoolean("fakeLogin", true)
} }
platformApiData?.entrySet()?.forEach { payload.putBundle("webRealmData", platformRealmData?.toBundle())
payload.putString(it.key, it.value.asString)
}
var hasErrors = false var hasErrors = false
credentials.forEach { (credential, b) -> credentials.forEach { (credential, b) ->

View File

@ -6,12 +6,12 @@ package pl.szczodrzynski.edziennik.ui.modules.login
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.google.gson.JsonObject
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
import pl.szczodrzynski.fslogin.realm.RealmData
object LoginInfo { object LoginInfo {
@ -191,9 +191,9 @@ object LoginInfo {
isDevOnly = true, isDevOnly = true,
isPlatformSelection = true, isPlatformSelection = true,
credentials = listOf( credentials = listOf(
getEmailCredential("webEmail"), getEmailCredential("email"),
FormField( FormField(
keyName = "webUsername", keyName = "username",
name = R.string.login_hint_username, name = R.string.login_hint_username,
icon = CommunityMaterial.Icon.cmd_account_outline, icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_username, emptyText = R.string.login_error_no_username,
@ -203,7 +203,7 @@ object LoginInfo {
validationRegex = "[A-Z]{7}[0-9]+", validationRegex = "[A-Z]{7}[0-9]+",
caseMode = FormField.CaseMode.UPPER_CASE caseMode = FormField.CaseMode.UPPER_CASE
), ),
getPasswordCredential("webPassword") getPasswordCredential("password")
), ),
errorCodes = mapOf() errorCodes = mapOf()
) )
@ -407,15 +407,12 @@ object LoginInfo {
data class Platform( data class Platform(
val id: Int, val id: Int,
val loginType: Int,
val loginMode: Int,
val name: String, val name: String,
val description: String?, val description: String?,
val guideText: String?,
val icon: String, val icon: String,
val screenshot: String?, val screenshot: String?,
val formFields: List<String>, val formFields: List<String>,
val apiData: JsonObject val realmData: RealmData
) )
open class BaseCredential( open class BaseCredential(

View File

@ -60,12 +60,12 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope {
adapter = LoginPlatformAdapter(activity) { platform -> adapter = LoginPlatformAdapter(activity) { platform ->
nav.navigate(R.id.loginFormFragment, Bundle( nav.navigate(R.id.loginFormFragment, Bundle(
"loginType" to platform.loginType, "loginType" to loginType,
"loginMode" to platform.loginMode, "loginMode" to loginMode,
"platformName" to platform.name, "platformName" to platform.name,
"platformDescription" to platform.description, "platformDescription" to platform.description,
"platformFormFields" to platform.formFields.joinToString(";"), "platformFormFields" to platform.formFields.joinToString(";"),
"platformApiData" to platform.apiData.toString() "platformRealmData" to app.gson.toJson(platform.realmData)
), activity.navOptions) ), activity.navOptions)
} }
@ -96,7 +96,7 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope {
val platforms = LoginInfo.platformList[mode.name] val platforms = LoginInfo.platformList[mode.name]
?: run { ?: run {
api.runCatching(activity) { api.runCatching(activity) {
getPlatforms(register.internalName) getRealms(register.internalName)
} ?: run { } ?: run {
nav.navigateUp() nav.navigateUp()
return@launch return@launch

View File

@ -258,7 +258,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
.setOnClickAction(() -> { .setOnClickAction(() -> {
new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName()); new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName(), false);
}) })
); );

View File

@ -121,19 +121,21 @@ class AttachmentsView @JvmOverloads constructor(
// open file by name, or first part before ':' (Vulcan OneDrive) // open file by name, or first part before ':' (Vulcan OneDrive)
Utils.openFile(context, File(Utils.getStorageDir(), attachment.name.substringBefore(":"))) Utils.openFile(context, File(Utils.getStorageDir(), attachment.name.substringBefore(":")))
return return
} } else if (attachment.name.contains(":")) {
Utils.openUrl(context, attachment.name.substringAfter(":"))
} else {
attachment.isDownloading = true
(adapter as? AttachmentAdapter)?.let {
it.notifyItemChanged(it.items.indexOf(attachment))
}
attachment.isDownloading = true EdziennikTask.attachmentGet(
(adapter as? AttachmentAdapter)?.let {
it.notifyItemChanged(it.items.indexOf(attachment))
}
EdziennikTask.attachmentGet(
attachment.profileId, attachment.profileId,
attachment.owner, attachment.owner,
attachment.id, attachment.id,
attachment.name attachment.name
).enqueue(context) ).enqueue(context)
}
} }
private val lastUpdate: Long = 0 private val lastUpdate: Long = 0

View File

@ -7,6 +7,10 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
<data>
<variable name="app" type="pl.szczodrzynski.edziennik.App"/>
</data>
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -41,6 +45,22 @@
android:text="Set last 10 as unseen" android:text="Set last 10 as unseen"
android:textAllCaps="false" /> android:textAllCaps="false" />
<Button
android:id="@+id/fullSync"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Full sync and empty profile"
android:textAllCaps="false" />
<Button
android:id="@+id/clearProfile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="Clear all profile data"
android:textAllCaps="false" />
<Button <Button
android:id="@+id/rodo" android:id="@+id/rodo"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -66,6 +86,7 @@
android:id="@+id/unarchive" android:id="@+id/unarchive"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Unarchive this profile" android:text="Unarchive this profile"
android:textAllCaps="false" /> android:textAllCaps="false" />
@ -74,6 +95,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" /> style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={app.config.archiverEnabled}"
android:text="Archiver enabled" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</layout> </layout>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.4.30' kotlin_version = '1.4.30'
release = [ release = [
versionName: "4.5", versionName: "4.6-beta.1",
versionCode: 4050099 versionCode: 4060001
] ]
setup = [ setup = [