mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-13 14:10:46 +02:00
Compare commits
102 Commits
v4.0-beta.
...
v4.0-beta.
Author | SHA1 | Date | |
---|---|---|---|
40cdc7d713 | |||
49825aca48 | |||
1d57c4e705 | |||
87ae5787ee | |||
20f16c25a3 | |||
6f1ec79d9b | |||
18c7eea89c | |||
f73060aeb6 | |||
2f653b83b6 | |||
445dec907d | |||
927316d24b | |||
3957453ed6 | |||
0296c704cb | |||
1e7fe972de | |||
c95bc656ea | |||
a082d95b04 | |||
6866dd4801 | |||
2186da416e | |||
22d859fcde | |||
39514b69b3 | |||
c384736840 | |||
507657f273 | |||
60641742ed | |||
0fc6f07986 | |||
1b2bdc0580 | |||
9bac239f77 | |||
371acb2d2a | |||
454f82e139 | |||
e8da249353 | |||
c7950c53da | |||
b5502478e4 | |||
4480a7e486 | |||
7c7dff743b | |||
c568cd3f2e | |||
6ec2bc6f21 | |||
af3b6f3a97 | |||
d855118610 | |||
c9992d9fe8 | |||
85fe2636cc | |||
35f4a31a76 | |||
1e494ebb70 | |||
ed93627505 | |||
b9b4b0036f | |||
4aa31424d6 | |||
8a825227cb | |||
cc1b581d7e | |||
9936d90ae2 | |||
df1a241b2b | |||
ae89b33fb7 | |||
e05b483f5c | |||
715f536b23 | |||
930813fb8a | |||
acd5e9b998 | |||
06011bf4ae | |||
30e15b813c | |||
fcd7a7f349 | |||
42ef40439e | |||
098beb14fe | |||
0b186a754a | |||
d00963b53d | |||
e282af0e80 | |||
630361849c | |||
88a1de50ca | |||
d8263d0b6a | |||
611ab0f100 | |||
70c307b796 | |||
054a233ad6 | |||
55268f1c43 | |||
1bec6d281c | |||
f17a02be54 | |||
4e8fdd2225 | |||
59819b4a96 | |||
673378d8d9 | |||
30044d6b21 | |||
ee43d40680 | |||
1354faf8c7 | |||
1bfb3781ab | |||
d7d0c6f822 | |||
2bea18dc3c | |||
f998f2d956 | |||
faa77ee5fb | |||
88ec463284 | |||
b7df71d7d9 | |||
6a28dbd2c4 | |||
010f7fa1fe | |||
209f98594f | |||
54121c99a3 | |||
f6f1370edf | |||
d5863485f9 | |||
afc88d316b | |||
b141279811 | |||
1997ea25d5 | |||
f4b49eecd4 | |||
a4493ec964 | |||
af8bda9e92 | |||
06d252e4ca | |||
67be456bb0 | |||
aa5e225148 | |||
367f46fac8 | |||
d2f14093ec | |||
43ed621879 | |||
15c8134d13 |
@ -166,8 +166,8 @@ dependencies {
|
||||
//implementation project(":Navigation")
|
||||
implementation project(":szkolny-font")
|
||||
|
||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
|
||||
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
|
||||
//releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
|
||||
|
||||
//implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT'
|
||||
//implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1'
|
||||
@ -190,6 +190,8 @@ dependencies {
|
||||
implementation 'com.github.jetradarmobile:android-snowfall:1.2.0'
|
||||
|
||||
implementation "io.coil-kt:coil:0.9.2"
|
||||
|
||||
implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -62,7 +62,7 @@
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/AppTheme.NoDisplay">
|
||||
android:theme="@style/AppTheme.Dark.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
@ -84,7 +84,7 @@
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/AppTheme.NoDisplay" />
|
||||
android:theme="@style/AppTheme.Dark.NoDisplay" />
|
||||
<!-- NOTIFICATIONS -->
|
||||
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
|
||||
android:label="@string/widget_notifications_title">
|
||||
|
@ -1,19 +1,23 @@
|
||||
<h3>Wersja 4.0-beta.9, 2020-02-19</h3>
|
||||
<h3>Wersja 4.0-beta.14, 2020-03-24</h3>
|
||||
<ul>
|
||||
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkosć oraz poprawność pobieranych danych.</li>
|
||||
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych.</li>
|
||||
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏</li>
|
||||
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</li>
|
||||
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
|
||||
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
|
||||
<li>Nowe <b>Oceny</b> - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej</li>
|
||||
<li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
|
||||
<br>
|
||||
<br>
|
||||
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
|
||||
<li>Nowe, przyjemniejsze powiadomienia</li>
|
||||
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
|
||||
<li>Dużo poprawek w widoku <b>Wiadomości</b> oraz <b>Ogłoszeń</b></li>
|
||||
<li>Częściowa <b>Obsługa dziennika EduDziennik</b></li>
|
||||
<li>Librus: opcja logowania w dziennikach <b>Jednostek Samorządu Terytorialnego</b> oraz <b>Oświata w Radomiu</b></li>
|
||||
<li>Librus: <b>poprawione obliczanie frekwencji</b></li>
|
||||
<li>Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)</li>
|
||||
<li>Lepsze <b>przekazywanie powiadomień na komputer</b> oraz łatwiejsze parowanie</li>
|
||||
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
|
||||
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
|
||||
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
|
||||
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
|
||||
@ -26,7 +30,6 @@
|
||||
Staramy się usuwać takie przypadki, jednak na chwilę obecną mogą występować błędy w:
|
||||
<ul>
|
||||
<li>Wysyłanie wiadomości może nie działać w pełni prawidłowo - proszę o zgłaszanie wszystkich błędów na naszym serwerze Discord</li>
|
||||
<li>Cisza nocna w powiadomieniach jeszcze nie działa.</li>
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/*secret password - removed for source code publication*/
|
||||
static toys AES_IV[16] = {
|
||||
0xe3, 0x65, 0x9e, 0xe5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
0xc4, 0x97, 0xfb, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||
|
||||
|
@ -45,7 +45,9 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
|
||||
import pl.szczodrzynski.edziennik.utils.*
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.TimetableManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -60,10 +62,13 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
get() = profile.id
|
||||
|
||||
var devMode = false
|
||||
var debugMode = false
|
||||
}
|
||||
|
||||
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
|
||||
val userActionManager by lazy { UserActionManager(this) }
|
||||
val gradesManager by lazy { GradesManager(this) }
|
||||
val timetableManager by lazy { TimetableManager(this) }
|
||||
|
||||
val db
|
||||
get() = App.db
|
||||
@ -103,7 +108,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
builder.installHttpsSupport(this)
|
||||
|
||||
if (devMode || BuildConfig.DEBUG) {
|
||||
if (debugMode || BuildConfig.DEBUG) {
|
||||
HyperLog.initialize(this)
|
||||
HyperLog.setLogLevel(Log.VERBOSE)
|
||||
HyperLog.setLogFormat(DebugLogFormat(this))
|
||||
@ -158,6 +163,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
Iconics.registerFont(SzkolnyFont)
|
||||
App.db = AppDb(this)
|
||||
Themes.themeInt = config.ui.theme
|
||||
debugMode = config.debugMode
|
||||
MHttp.instance().customOkHttpClient(http)
|
||||
|
||||
if (!profileLoadById(config.lastProfileId)) {
|
||||
@ -174,6 +180,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
|
||||
if (config.devModePassword != null)
|
||||
checkDevModePassword()
|
||||
debugMode = devMode || config.debugMode
|
||||
|
||||
if (config.sync.enabled)
|
||||
SyncWorker.scheduleNext(this@App, false)
|
||||
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik;
|
||||
|
||||
import android.graphics.Paint;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.databinding.BindingAdapter;
|
||||
|
||||
public class Binding {
|
||||
@BindingAdapter("strikeThrough")
|
||||
public static void strikeThrough(TextView textView, Boolean strikeThrough) {
|
||||
if (strikeThrough) {
|
||||
textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
} else {
|
||||
textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
}
|
||||
}
|
||||
}
|
20
app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
Normal file
20
app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||
*/
|
||||
package pl.szczodrzynski.edziennik
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.widget.TextView
|
||||
import androidx.databinding.BindingAdapter
|
||||
|
||||
object Binding {
|
||||
@JvmStatic
|
||||
@BindingAdapter("strikeThrough")
|
||||
fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
|
||||
if (strikeThrough) {
|
||||
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||
} else {
|
||||
textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ package pl.szczodrzynski.edziennik
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
@ -10,6 +12,7 @@ import android.content.res.Resources
|
||||
import android.database.Cursor
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
@ -23,6 +26,7 @@ import android.util.Base64
|
||||
import android.util.Base64.NO_WRAP
|
||||
import android.util.Base64.encodeToString
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RadioButton
|
||||
@ -40,6 +44,7 @@ import com.google.android.gms.security.ProviderInstaller
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import im.wangchao.mhttp.Response
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
@ -50,6 +55,7 @@ import okhttp3.RequestBody
|
||||
import okhttp3.TlsVersion
|
||||
import okio.Buffer
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
@ -94,8 +100,8 @@ fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) n
|
||||
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?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonNull) null else it.asJsonObject }
|
||||
fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonNull) null else it.asJsonArray }
|
||||
fun JsonObject?.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
|
||||
@ -103,8 +109,19 @@ fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if
|
||||
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?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonObject } ?: defaultValue
|
||||
fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonArray } ?: defaultValue
|
||||
fun JsonObject?.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.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null }
|
||||
fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null }
|
||||
|
||||
fun String.toJsonObject(): JsonObject? = try { JsonParser().parse(this).asJsonObject } catch (ignore: Exception) { null }
|
||||
|
||||
operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value)
|
||||
operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value)
|
||||
@ -1068,3 +1085,84 @@ fun Throwable.toErrorCode() = when (this) {
|
||||
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
|
||||
else -> ERROR_API_EXCEPTION
|
||||
}
|
||||
fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this)
|
||||
|
||||
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
||||
if (a != null && b != null) {
|
||||
return code(a, b)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmName("averageOrNullOfInt")
|
||||
fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
||||
@kotlin.jvm.JvmName("averageOrNullOfFloat")
|
||||
fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
||||
|
||||
fun String.copyToClipboard(context: Context) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText("Tekst", this)
|
||||
clipboard.primaryClip = clipData
|
||||
}
|
||||
|
||||
fun TextView.getTextPosition(range: IntRange): Rect {
|
||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
|
||||
// Initialize global value
|
||||
var parentTextViewRect = Rect()
|
||||
|
||||
// Initialize values for the computing of clickedText position
|
||||
//val completeText = parentTextView.text as SpannableString
|
||||
val textViewLayout = this.layout
|
||||
|
||||
val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText)
|
||||
val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText)
|
||||
var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText)
|
||||
var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText)
|
||||
|
||||
// Get the rectangle of the clicked text
|
||||
val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText)
|
||||
val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText)
|
||||
val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset
|
||||
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect)
|
||||
|
||||
// Update the rectangle position to his real position on screen
|
||||
val parentTextViewLocation = intArrayOf(0, 0)
|
||||
this.getLocationOnScreen(parentTextViewLocation)
|
||||
|
||||
val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop)
|
||||
parentTextViewRect.top += parentTextViewTopAndBottomOffset
|
||||
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset
|
||||
|
||||
// In the case of multi line text, we have to choose what rectangle take
|
||||
if (keywordIsInMultiLine) {
|
||||
val screenHeight = windowManager.defaultDisplay.height
|
||||
val dyTop = parentTextViewRect.top
|
||||
val dyBottom = screenHeight - parentTextViewRect.bottom
|
||||
val onTop = dyTop > dyBottom
|
||||
|
||||
if (onTop) {
|
||||
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
|
||||
} else {
|
||||
parentTextViewRect = Rect()
|
||||
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
|
||||
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
|
||||
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
|
||||
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
|
||||
}
|
||||
}
|
||||
|
||||
parentTextViewRect.left += (
|
||||
parentTextViewLocation[0] +
|
||||
startXCoordinatesOfClickedText +
|
||||
this.compoundPaddingLeft -
|
||||
this.scrollX
|
||||
).toInt()
|
||||
parentTextViewRect.right = (
|
||||
parentTextViewRect.left +
|
||||
endXCoordinatesOfClickedText -
|
||||
startXCoordinatesOfClickedText
|
||||
).toInt()
|
||||
|
||||
return parentTextViewRect
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
@ -42,7 +41,6 @@ import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.*
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
|
||||
@ -51,6 +49,7 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
|
||||
@ -286,8 +285,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
setContentView(b.root)
|
||||
|
||||
Log.d(TAG, Signing.appPassword)
|
||||
|
||||
mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
|
||||
@ -448,6 +445,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
// WHAT'S NEW DIALOG
|
||||
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
||||
// force an AppSync after update
|
||||
app.config.sync.lastAppSync = 0L
|
||||
ChangelogDialog(this)
|
||||
if (app.config.appVersion < 170) {
|
||||
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
|
||||
@ -567,7 +566,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
EdziennikTask.syncProfile(
|
||||
App.profileId,
|
||||
listOf(navTargetId to fragmentParam),
|
||||
arguments
|
||||
arguments = arguments
|
||||
).enqueue(this)
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@ -725,6 +724,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
)
|
||||
true
|
||||
}
|
||||
"createManualEvent" -> {
|
||||
val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday()
|
||||
EventManualDialog(
|
||||
this,
|
||||
App.profileId,
|
||||
defaultDate = date
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
if (handled && !navLoading) {
|
||||
@ -906,7 +914,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
bottomSheet.close()
|
||||
bottomSheet.removeAllContextual()
|
||||
bottomSheet.toggleGroupEnabled = false
|
||||
bottomSheet.onCloseListener = null
|
||||
drawer.close()
|
||||
drawer.setSelection(target.id, fireOnClick = false)
|
||||
navView.toolbar.setTitle(target.title ?: target.name)
|
||||
@ -1144,7 +1151,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
private var targetHomeId: Int = -1
|
||||
override fun onBackPressed() {
|
||||
if (!b.navView.onBackPressed()) {
|
||||
if (App.config.ui.openDrawerOnBackPressed) {
|
||||
if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome)
|
||||
|| navTarget.id == DRAWER_ITEM_HOME)) {
|
||||
b.navView.drawer.toggle()
|
||||
} else {
|
||||
navigateUp()
|
||||
|
@ -22,7 +22,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
companion object {
|
||||
const val DATA_VERSION = 10
|
||||
const val DATA_VERSION = 11
|
||||
}
|
||||
|
||||
private val job = Job()
|
||||
@ -75,6 +75,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
|
||||
set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
|
||||
|
||||
private var mDebugMode: Boolean? = null
|
||||
var debugMode: Boolean
|
||||
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
|
||||
set(value) { set("debugMode", value); mDebugMode = value }
|
||||
|
||||
private var mDevModePassword: String? = null
|
||||
var devModePassword: String?
|
||||
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }
|
||||
@ -100,11 +105,6 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
||||
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
||||
|
||||
private var mLastAppSync: Long? = null
|
||||
var lastAppSync: Long
|
||||
get() { mLastAppSync = mLastAppSync ?: values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
|
||||
set(value) { set("lastAppSync", value); mLastAppSync = value }
|
||||
|
||||
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||
init {
|
||||
|
@ -6,17 +6,11 @@ package pl.szczodrzynski.edziennik.config
|
||||
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
|
||||
|
||||
class ConfigGrades(private val config: Config) {
|
||||
companion object {
|
||||
const val ORDER_BY_DATE_DESC = 0
|
||||
const val ORDER_BY_SUBJECT_ASC = 1
|
||||
const val ORDER_BY_DATE_ASC = 2
|
||||
const val ORDER_BY_SUBJECT_DESC = 3
|
||||
}
|
||||
|
||||
private var mOrderBy: Int? = null
|
||||
var orderBy: Int
|
||||
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: ORDER_BY_DATE_DESC }
|
||||
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC }
|
||||
set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.config
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.getIntList
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class ConfigSync(private val config: Config) {
|
||||
private var mDontShowAppManagerDialog: Boolean? = null
|
||||
@ -19,6 +20,11 @@ class ConfigSync(private val config: Config) {
|
||||
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
|
||||
set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
|
||||
|
||||
private var mWebPushEnabled: Boolean? = null
|
||||
var webPushEnabled: Boolean
|
||||
get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true }
|
||||
set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value }
|
||||
|
||||
private var mSyncOnlyWifi: Boolean? = null
|
||||
var onlyWifi: Boolean
|
||||
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
|
||||
@ -34,20 +40,30 @@ class ConfigSync(private val config: Config) {
|
||||
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
|
||||
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
|
||||
|
||||
private var mLastAppSync: Long? = null
|
||||
var lastAppSync: Long
|
||||
get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
|
||||
set(value) { config.set("lastAppSync", value); mLastAppSync = value }
|
||||
|
||||
/* ____ _ _ _
|
||||
/ __ \ (_) | | | |
|
||||
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
|
||||
| | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __|
|
||||
| |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \
|
||||
\___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/
|
||||
private var mQuietHoursStart: Long? = null
|
||||
var quietHoursStart: Long
|
||||
get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", 0L); return mQuietHoursStart ?: 0L }
|
||||
private var mQuietHoursEnabled: Boolean? = null
|
||||
var quietHoursEnabled: Boolean
|
||||
get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false }
|
||||
set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value }
|
||||
|
||||
private var mQuietHoursStart: Time? = null
|
||||
var quietHoursStart: Time?
|
||||
get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart }
|
||||
set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value }
|
||||
|
||||
private var mQuietHoursEnd: Long? = null
|
||||
var quietHoursEnd: Long
|
||||
get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", 0L); return mQuietHoursEnd ?: 0L }
|
||||
private var mQuietHoursEnd: Time? = null
|
||||
var quietHoursEnd: Time?
|
||||
get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd }
|
||||
set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value }
|
||||
|
||||
private var mQuietDuringLessons: Boolean? = null
|
||||
@ -90,4 +106,4 @@ class ConfigSync(private val config: Config) {
|
||||
var tokenVulcanList: List<Int>
|
||||
get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
|
||||
set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.config
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.getIntList
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
|
||||
|
||||
class ConfigUI(private val config: Config) {
|
||||
private var mTheme: Int? = null
|
||||
@ -45,11 +44,6 @@ class ConfigUI(private val config: Config) {
|
||||
get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false }
|
||||
set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value }
|
||||
|
||||
private var mHomeCards: List<HomeCardModel>? = null
|
||||
var homeCards: List<HomeCardModel>
|
||||
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
|
||||
set(value) { config.set("homeCards", value); mHomeCards = value }
|
||||
|
||||
private var mSnowfall: Boolean? = null
|
||||
var snowfall: Boolean
|
||||
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
|
||||
|
@ -29,8 +29,8 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
|
||||
|
||||
val grades by lazy { ProfileConfigGrades(this) }
|
||||
val ui by lazy { ProfileConfigUI(this) }
|
||||
val sync by lazy { ProfileConfigSync(this) }
|
||||
/*
|
||||
val sync by lazy { ConfigSync(this) }
|
||||
val timetable by lazy { ConfigTimetable(this) }
|
||||
val grades by lazy { ConfigGrades(this) }*/
|
||||
|
||||
@ -56,4 +56,4 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
|
||||
db.configDao().add(ConfigEntry(profileId, key, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,10 @@
|
||||
package pl.szczodrzynski.edziennik.config
|
||||
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.getFloat
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
||||
|
||||
class ProfileConfigGrades(private val config: ProfileConfig) {
|
||||
private var mColorMode: Int? = null
|
||||
@ -20,8 +21,32 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
|
||||
get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
|
||||
set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
|
||||
|
||||
private var mCountZeroToAvg: Boolean? = null
|
||||
var countZeroToAvg: Boolean
|
||||
get() { mCountZeroToAvg = mCountZeroToAvg ?: config.values.get("countZeroToAvg", true); return mCountZeroToAvg ?: true }
|
||||
set(value) { config.set("countZeroToAvg", value); mCountZeroToAvg = value }
|
||||
private var mHideImproved: Boolean? = null
|
||||
var hideImproved: Boolean
|
||||
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
|
||||
set(value) { config.set("hideImproved", value); mHideImproved = value }
|
||||
|
||||
private var mAverageWithoutWeight: Boolean? = null
|
||||
var averageWithoutWeight: Boolean
|
||||
get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true }
|
||||
set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value }
|
||||
|
||||
private var mPlusValue: Float? = null
|
||||
var plusValue: Float?
|
||||
get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue }
|
||||
set(value) { config.set("plusValue", value); mPlusValue = value }
|
||||
private var mMinusValue: Float? = null
|
||||
var minusValue: Float?
|
||||
get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
|
||||
set(value) { config.set("minusValue", value); mMinusValue = value }
|
||||
|
||||
private var mDontCountEnabled: Boolean? = null
|
||||
var dontCountEnabled: Boolean
|
||||
get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false }
|
||||
set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value }
|
||||
|
||||
private var mDontCountGrades: List<String>? = null
|
||||
var dontCountGrades: List<String>
|
||||
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
|
||||
set(value) { config.set("dontCountGrades", value); mDontCountGrades = value }
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.config
|
||||
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
|
||||
class ProfileConfigSync(private val config: ProfileConfig) {
|
||||
private var mNotificationFilter: List<Int>? = null
|
||||
var notificationFilter: List<Int>
|
||||
get() { mNotificationFilter = mNotificationFilter ?: config.values.get("notificationFilter", listOf()); return mNotificationFilter ?: listOf() }
|
||||
set(value) { config.set("notificationFilter", value); mNotificationFilter = value }
|
||||
}
|
@ -7,10 +7,16 @@ package pl.szczodrzynski.edziennik.config
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
|
||||
|
||||
class ProfileConfigUI(private val config: ProfileConfig) {
|
||||
private var mAgendaViewType: Int? = null
|
||||
var agendaViewType: Int
|
||||
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
|
||||
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
|
||||
|
||||
private var mHomeCards: List<HomeCardModel>? = null
|
||||
var homeCards: List<HomeCardModel>
|
||||
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
|
||||
set(value) { config.set("homeCards", value); mHomeCards = value }
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import kotlin.math.abs
|
||||
|
||||
class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
|
||||
init { config.apply {
|
||||
@ -42,9 +43,7 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
|
||||
devModePassword = p.getString("$s.devModePassword", null).fix()
|
||||
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
|
||||
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
|
||||
sync.quietHoursStart = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
|
||||
appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0
|
||||
sync.quietHoursEnd = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
|
||||
timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false
|
||||
ui.headerBackground = p.getString("$s.headerBackground", null).fix()
|
||||
ui.appBackground = p.getString("$s.appBackground", null).fix()
|
||||
@ -59,6 +58,22 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
|
||||
sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true
|
||||
timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) }
|
||||
|
||||
val startMillis = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
|
||||
val endMillis = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
|
||||
if (startMillis > 0) {
|
||||
try {
|
||||
sync.quietHoursStart = Time.fromMillis(abs(startMillis))
|
||||
sync.quietHoursEnd = Time.fromMillis(abs(endMillis))
|
||||
sync.quietHoursEnabled = true
|
||||
}
|
||||
catch (_: Exception) {}
|
||||
}
|
||||
else {
|
||||
sync.quietHoursEnabled = false
|
||||
sync.quietHoursStart = null
|
||||
sync.quietHoursEnd = null
|
||||
}
|
||||
|
||||
sync.tokenMobidziennikList = listOf()
|
||||
sync.tokenVulcanList = listOf()
|
||||
sync.tokenLibrusList = listOf()
|
||||
@ -78,4 +93,4 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
|
||||
private fun String?.fix(): String? {
|
||||
return this?.replace("\"", "")?.let { if (it == "null") null else it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,10 +94,14 @@ fun HashMap<String, String?>.getLongList(key: String, default: List<Long>?): Lis
|
||||
return this[key]?.let { gson.fromJson<List<Long>>(it, object: TypeToken<List<Long>>(){}.type) } ?: default
|
||||
}
|
||||
|
||||
fun HashMap<String, String?>.getFloat(key: String): Float? {
|
||||
return this[key]?.toFloatOrNull()
|
||||
}
|
||||
|
||||
fun List<ConfigEntry>.toHashMap(profileId: Int, map: HashMap<String, String?>) {
|
||||
map.clear()
|
||||
forEach {
|
||||
if (it.profileId == profileId)
|
||||
map[it.key] = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,9 @@ import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.HOUR
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.config.Config
|
||||
import pl.szczodrzynski.edziennik.config.ConfigGrades.Companion.ORDER_BY_DATE_DESC
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import kotlin.math.abs
|
||||
|
||||
class ConfigMigration(app: App, config: Config) {
|
||||
init { config.apply {
|
||||
@ -43,8 +45,9 @@ class ConfigMigration(app: App, config: Config) {
|
||||
sync.interval = 1*HOUR.toInt()
|
||||
sync.notifyAboutUpdates = true
|
||||
sync.onlyWifi = false
|
||||
sync.quietHoursStart = 0
|
||||
sync.quietHoursEnd = 0
|
||||
sync.quietHoursEnabled = false
|
||||
sync.quietHoursStart = null
|
||||
sync.quietHoursEnd = null
|
||||
sync.quietDuringLessons = false
|
||||
sync.tokenApp = null
|
||||
sync.tokenMobidziennik = null
|
||||
@ -63,12 +66,31 @@ class ConfigMigration(app: App, config: Config) {
|
||||
|
||||
if (dataVersion < 10) {
|
||||
ui.openDrawerOnBackPressed = false
|
||||
ui.homeCards = listOf()
|
||||
ui.snowfall = false
|
||||
ui.bottomSheetOpened = false
|
||||
sync.dontShowAppManagerDialog = false
|
||||
|
||||
dataVersion = 10
|
||||
}
|
||||
|
||||
if (dataVersion < 11) {
|
||||
val startMillis = config.values.get("quietHoursStart", 0L)
|
||||
val endMillis = config.values.get("quietHoursEnd", 0L)
|
||||
if (startMillis > 0) {
|
||||
try {
|
||||
sync.quietHoursStart = Time.fromMillis(abs(startMillis))
|
||||
sync.quietHoursEnd = Time.fromMillis(abs(endMillis))
|
||||
sync.quietHoursEnabled = true
|
||||
}
|
||||
catch (_: Exception) {}
|
||||
}
|
||||
else {
|
||||
sync.quietHoursEnabled = false
|
||||
sync.quietHoursStart = null
|
||||
sync.quietHoursEnd = null
|
||||
}
|
||||
|
||||
dataVersion = 11
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -6,19 +6,19 @@ package pl.szczodrzynski.edziennik.config.utils
|
||||
|
||||
import pl.szczodrzynski.edziennik.config.ProfileConfig
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
||||
|
||||
class ProfileConfigMigration(config: ProfileConfig) {
|
||||
init { config.apply {
|
||||
|
||||
if (dataVersion < 1) {
|
||||
grades.colorMode = COLOR_MODE_WEIGHTED
|
||||
grades.countZeroToAvg = true
|
||||
grades.dontCountEnabled = false
|
||||
grades.yearAverageMode = YEAR_ALL_GRADES
|
||||
ui.agendaViewType = AGENDA_DEFAULT
|
||||
|
||||
dataVersion = 1
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask
|
||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.toApiError
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
@ -181,7 +182,7 @@ class ApiService : Service() {
|
||||
is SzkolnyTask -> task.run(taskCallback)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
|
||||
taskCallback.onError(e.toApiError(TAG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,9 @@ const val FAKE_LIBRUS_TOKEN = "https://librus.szkolny.eu/access_token.php"
|
||||
const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login="
|
||||
const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
|
||||
|
||||
val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp"
|
||||
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
|
||||
const val LIBRUS_CLIENT_ID = "6XPsKf10LPz1nxgHQLcvZ1KM48DYzlBAhxipaXY8"
|
||||
const val LIBRUS_REDIRECT_URL = "http://localhost/bar"
|
||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
||||
@ -60,6 +60,7 @@ const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT
|
||||
const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik"
|
||||
const val IDZIENNIK_WEB_LOGIN = "login.aspx"
|
||||
const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx"
|
||||
const val IDZIENNIK_WEB_HOME = "mod_panelRodzica/StronaGlowna.aspx"
|
||||
const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec"
|
||||
const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia"
|
||||
const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia"
|
||||
|
@ -7,7 +7,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.EndpointTimer
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_NEVER
|
||||
|
||||
fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featureIds: List<Int>, viewId: Int?) {
|
||||
fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?) {
|
||||
val data = this
|
||||
|
||||
val possibleLoginMethods = data.loginMethods.toMutableList()
|
||||
@ -46,13 +46,18 @@ fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featu
|
||||
// add all endpoint IDs and required login methods, filtering using timers
|
||||
.onEach { feature ->
|
||||
feature.endpointIds.forEach { endpoint ->
|
||||
if (onlyEndpoints?.contains(endpoint.first) == false)
|
||||
return@forEach
|
||||
(data.endpointTimers
|
||||
.singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id
|
||||
?: -1, endpoint.first))
|
||||
.let { timer ->
|
||||
if (timer.nextSync == SYNC_ALWAYS ||
|
||||
(viewId != null && timer.viewId == viewId) ||
|
||||
(timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp)) {
|
||||
if (
|
||||
onlyEndpoints?.contains(endpoint.first) == true ||
|
||||
timer.nextSync == SYNC_ALWAYS ||
|
||||
viewId != null && timer.viewId == viewId ||
|
||||
timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp
|
||||
) {
|
||||
data.targetEndpointIds[endpoint.first] = timer.lastSync
|
||||
requiredLoginMethods.add(endpoint.second)
|
||||
}
|
||||
|
@ -122,6 +122,10 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
|
||||
const val ERROR_LIBRUS_API_MAINTENANCE = 181
|
||||
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
|
||||
const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183
|
||||
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184
|
||||
const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185
|
||||
const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186
|
||||
const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187
|
||||
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
|
||||
@ -138,6 +142,7 @@ const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217
|
||||
const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218
|
||||
|
||||
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
|
||||
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.data.api
|
||||
|
||||
import kotlin.text.RegexOption.DOT_MATCHES_ALL
|
||||
import kotlin.text.RegexOption.IGNORE_CASE
|
||||
|
||||
object Regexes {
|
||||
val STYLE_CSS_COLOR by lazy {
|
||||
@ -109,6 +110,13 @@ object Regexes {
|
||||
val IDZIENNIK_MESSAGES_RECIPIENT_PARENT by lazy {
|
||||
"""(.+?)\s\((.+)\)""".toRegex()
|
||||
}
|
||||
/*<span id="ctl00_spanSzczesliwyLos">Szczęśliwy los na dzisiaj to <b>19</b>. Los na jutro to <b>22</b></span>*/
|
||||
val IDZIENNIK_WEB_LUCKY_NUMBER by lazy {
|
||||
"""dzisiaj to <b>([0-9]+)</b>""".toRegex()
|
||||
}
|
||||
val IDZIENNIK_WEB_SELECTED_REGISTER by lazy {
|
||||
"""selected="selected" value="([0-9]+)" data-id-ucznia""".toRegex()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -119,7 +127,7 @@ object Regexes {
|
||||
|
||||
|
||||
val LIBRUS_ATTACHMENT_KEY by lazy {
|
||||
"""singleUseKey=([0-9A-f_]+)""".toRegex()
|
||||
"""singleUseKey=([0-9A-z_]+)""".toRegex()
|
||||
}
|
||||
|
||||
|
||||
@ -192,4 +200,20 @@ object Regexes {
|
||||
val EDUDZIENNIK_TEACHERS by lazy {
|
||||
"""<div class="teacher">.*?<p>(.+?) (.+?)</p>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
val LINKIFY_DATE_YMD by lazy {
|
||||
"""(1\d{3}|20\d{2})[\-./](1[0-2]|0?\d)[\-./]([1-2]\d|3[0-1]|0?\d)""".toRegex()
|
||||
}
|
||||
val LINKIFY_DATE_DMY by lazy {
|
||||
"""(?<![\d\-./])([1-2]\d|3[0-1]|0?\d)[\-./](1[0-2]|0?\d)(?:[\-./](1\d{3}|2?0?\d{2}))?(?![\d\-/])""".toRegex()
|
||||
}
|
||||
val LINKIFY_DATE_ABSOLUTE by lazy {
|
||||
"""([1-3][0-9]|[1-9])\s(sty|lut|mar|kwi|maj|cze|lip|sie|wrz|paź|lis|gru).*?\s(1[0-9]{3}|20[0-9]{2})?""".toRegex(IGNORE_CASE)
|
||||
}
|
||||
val LINKIFY_DATE_RELATIVE by lazy {
|
||||
"""za\s([0-9]+)?\s?(dni|dzień|tydzień|tygodnie)""".toRegex(IGNORE_CASE)
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore))
|
||||
fun sync() = EdziennikTask(-1, SyncRequest())
|
||||
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, arguments))
|
||||
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null, onlyEndpoints: List<Int>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, onlyEndpoints, arguments))
|
||||
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
|
||||
fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
|
||||
fun messageSend(profileId: Int, recipients: List<Teacher>, subject: String, text: String) = EdziennikTask(profileId, MessageSendRequest(recipients, subject, text))
|
||||
@ -85,6 +85,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) }
|
||||
?: Features.getAllIds(),
|
||||
viewId = request.viewIds?.get(0)?.first,
|
||||
onlyEndpoints = request.onlyEndpoints,
|
||||
arguments = request.arguments)
|
||||
is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
|
||||
is MessageSendRequest -> edziennikInterface?.sendMessage(request.recipients, request.subject, request.text)
|
||||
@ -106,7 +107,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
data class FirstLoginRequest(val loginStore: LoginStore)
|
||||
class SyncRequest
|
||||
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null, val arguments: JsonObject? = null)
|
||||
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null, val onlyEndpoints: List<Int>? = null, val arguments: JsonObject? = null)
|
||||
data class SyncProfileListRequest(val profileList: List<Int>)
|
||||
data class MessageGetRequest(val message: MessageFull)
|
||||
data class MessageSendRequest(val recipients: List<Teacher>, val subject: String, val text: String)
|
||||
|
@ -52,9 +52,9 @@ class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStor
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(edudziennikLoginMethods, EdudziennikFeatures, featureIds, viewId)
|
||||
data.prepare(edudziennikLoginMethods, EdudziennikFeatures, featureIds, viewId, onlyEndpoints)
|
||||
login()
|
||||
}
|
||||
|
||||
|
@ -27,15 +27,15 @@ class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_EDUDZIENNIK_WEB_START -> {
|
||||
|
@ -27,8 +27,15 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
|
||||
private const val TAG = "EdudziennikWebAttendance"
|
||||
}
|
||||
|
||||
init { data.profile?.also { profile ->
|
||||
webGet(TAG, data.studentEndpoint + "Presence", semester = -1) { text ->
|
||||
private var requestSemester: Int? = null
|
||||
|
||||
init {
|
||||
if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1
|
||||
getAttendances()
|
||||
}
|
||||
|
||||
private fun getAttendances() { data.profile?.also { profile ->
|
||||
webGet(TAG, data.studentEndpoint + "Presence", semester = requestSemester) { text ->
|
||||
|
||||
val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map {
|
||||
val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim())
|
||||
@ -91,8 +98,13 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
|
||||
}
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE)
|
||||
if (profile.empty && requestSemester == 1 && data.currentSemester == 2) {
|
||||
requestSemester = null
|
||||
getAttendances()
|
||||
} else {
|
||||
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE)
|
||||
}
|
||||
}
|
||||
} ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) }
|
||||
}
|
||||
|
@ -14,7 +14,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZI
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
@ -90,7 +95,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
||||
val columnName = info.child(4).text().trim()
|
||||
val comment = info.ownText()
|
||||
|
||||
val description = columnName + if (comment.isNotBlank()) " - $comment" else ""
|
||||
val description = columnName + if (comment.isNotBlank()) " - $comment" else null
|
||||
|
||||
val teacherName = info.child(1).text()
|
||||
val teacher = data.getTeacherByLastFirst(teacherName)
|
||||
@ -109,20 +114,20 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
||||
} ?: -1
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
fullName,
|
||||
color,
|
||||
description,
|
||||
name,
|
||||
value,
|
||||
if (gradeCountToAverage) weight else 0f,
|
||||
semester,
|
||||
teacher.id,
|
||||
subject.id
|
||||
).apply {
|
||||
type = gradeType
|
||||
}
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = gradeType,
|
||||
value = value,
|
||||
weight = if (gradeCountToAverage) weight else 0f,
|
||||
color = color,
|
||||
category = fullName,
|
||||
description = description,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacher.id,
|
||||
subjectId = subject.id
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -139,23 +144,23 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
||||
|
||||
if (proposed != null && proposed.isNotBlank()) {
|
||||
val proposedGradeObject = Grade(
|
||||
profileId,
|
||||
(-1 * subject.id) - 1,
|
||||
"",
|
||||
-1,
|
||||
"",
|
||||
proposed,
|
||||
proposed.toFloatOrNull() ?: 0f,
|
||||
0f,
|
||||
semester,
|
||||
-1,
|
||||
subject.id
|
||||
).apply {
|
||||
type = when (semester) {
|
||||
1 -> TYPE_SEMESTER1_PROPOSED
|
||||
else -> TYPE_SEMESTER2_PROPOSED
|
||||
}
|
||||
}
|
||||
profileId = profileId,
|
||||
id = (-1 * subject.id) - 1,
|
||||
name = proposed,
|
||||
type = when (semester) {
|
||||
1 -> TYPE_SEMESTER1_PROPOSED
|
||||
else -> TYPE_SEMESTER2_PROPOSED
|
||||
},
|
||||
value = proposed.toFloatOrNull() ?: 0f,
|
||||
weight = 0f,
|
||||
color = -1,
|
||||
category = null,
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = -1,
|
||||
subjectId = subject.id
|
||||
)
|
||||
|
||||
data.gradeList.add(proposedGradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -172,23 +177,23 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
||||
|
||||
if (final != null && final.isNotBlank()) {
|
||||
val finalGradeObject = Grade(
|
||||
profileId,
|
||||
(-1 * subject.id) - 2,
|
||||
"",
|
||||
-1,
|
||||
"",
|
||||
final,
|
||||
final.toFloatOrNull() ?: 0f,
|
||||
0f,
|
||||
semester,
|
||||
-1,
|
||||
subject.id
|
||||
).apply {
|
||||
type = when (semester) {
|
||||
1 -> TYPE_SEMESTER1_FINAL
|
||||
else -> TYPE_SEMESTER2_FINAL
|
||||
}
|
||||
}
|
||||
profileId = profileId,
|
||||
id = (-1 * subject.id) - 2,
|
||||
name = final,
|
||||
type = when (semester) {
|
||||
1 -> TYPE_SEMESTER1_FINAL
|
||||
else -> TYPE_SEMESTER2_FINAL
|
||||
},
|
||||
value = final.toFloatOrNull() ?: 0f,
|
||||
weight = 0f,
|
||||
color = -1,
|
||||
category = null,
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = -1,
|
||||
subjectId = subject.id
|
||||
)
|
||||
|
||||
data.gradeList.add(finalGradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
|
@ -81,6 +81,11 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
|
||||
get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth }
|
||||
set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value }
|
||||
|
||||
private var mWebSelectedRegister: Int? = null
|
||||
var webSelectedRegister: Int
|
||||
get() { mWebSelectedRegister = mWebSelectedRegister ?: loginStore.getLoginData("webSelectedRegister", 0); return mWebSelectedRegister ?: 0 }
|
||||
set(value) { loginStore.putLoginData("webSelectedRegister", value); mWebSelectedRegister = value }
|
||||
|
||||
/* _
|
||||
/\ (_)
|
||||
/ \ _ __ _
|
||||
|
@ -54,9 +54,9 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId)
|
||||
data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId, onlyEndpoints)
|
||||
login()
|
||||
}
|
||||
|
||||
|
@ -30,15 +30,15 @@ class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_IDZIENNIK_WEB_TIMETABLE -> {
|
||||
|
@ -13,6 +13,7 @@ import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSwitchRegister
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.io.File
|
||||
@ -48,6 +49,17 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
|
||||
return
|
||||
}
|
||||
|
||||
if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GRADES) {
|
||||
// special override for accounts where displaying grades
|
||||
// for another student requires switching it manually
|
||||
if (data.registerId != data.webSelectedRegister) {
|
||||
IdziennikWebSwitchRegister(data, data.registerId) {
|
||||
webApiGet(tag, endpoint, parameters, onSuccess)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED
|
||||
response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
|
||||
@ -115,7 +127,7 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) {
|
||||
fun webGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (text: String) -> Unit) {
|
||||
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint")
|
||||
|
||||
val callback = object : TextCallbackHandler() {
|
||||
@ -160,7 +172,14 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
|
||||
Request.builder()
|
||||
.url("$IDZIENNIK_WEB_URL/$endpoint")
|
||||
.userAgent(IDZIENNIK_USER_AGENT)
|
||||
.get()
|
||||
.apply {
|
||||
if (parameters.isEmpty()) get()
|
||||
else post()
|
||||
|
||||
parameters.map { (name, value) ->
|
||||
addParameter(name, value)
|
||||
}
|
||||
}
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
|
@ -69,7 +69,7 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik,
|
||||
|
||||
val luckyNumberObject = LuckyNumber(
|
||||
data.profileId,
|
||||
Date.getToday(),
|
||||
luckyNumberDate,
|
||||
luckyNumber
|
||||
)
|
||||
|
||||
|
@ -16,6 +16,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Announcement
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getJsonObject
|
||||
import pl.szczodrzynski.edziennik.getLong
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class IdziennikWebAnnouncements(override val data: DataIdziennik,
|
||||
@ -43,11 +45,11 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik,
|
||||
for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
|
||||
val jAnnouncement = jAnnouncementEl.asJsonObject
|
||||
// jAnnouncement
|
||||
val announcementId = jAnnouncement.get("Id").asLong
|
||||
val announcementId = jAnnouncement.getLong("Id") ?: -1
|
||||
|
||||
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.get("Autor").asString)
|
||||
val addedDate = java.lang.Long.parseLong(jAnnouncement.get("DataDodania").asString.replace("[^\\d]".toRegex(), ""))
|
||||
val startDate = Date.fromMillis(java.lang.Long.parseLong(jAnnouncement.get("DataWydarzenia").asString.replace("[^\\d]".toRegex(), "")))
|
||||
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "")
|
||||
val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis()
|
||||
val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) }
|
||||
|
||||
val announcementObject = Announcement(
|
||||
profileId,
|
||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import java.util.*
|
||||
|
||||
class IdziennikWebExams(override val data: DataIdziennik,
|
||||
override val lastSync: Long?,
|
||||
@ -70,9 +71,12 @@ class IdziennikWebExams(override val data: DataIdziennik,
|
||||
val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate)
|
||||
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
val eventType = when (exam.getString("rodzaj")) {
|
||||
"sprawdzian/praca klasowa" -> Event.TYPE_EXAM
|
||||
else -> Event.TYPE_SHORT_QUIZ
|
||||
val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) {
|
||||
"sprawdzian/praca klasowa",
|
||||
"sprawdzian",
|
||||
"praca klasowa" -> Event.TYPE_EXAM
|
||||
"kartkówka" -> Event.TYPE_SHORT_QUIZ
|
||||
else -> Event.TYPE_EXAM
|
||||
}
|
||||
|
||||
val eventObject = Event(
|
||||
|
@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
@ -63,17 +64,19 @@ class IdziennikWebGrades(override val data: DataIdziennik,
|
||||
}
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category,
|
||||
colorInt,
|
||||
"",
|
||||
name,
|
||||
value,
|
||||
weight,
|
||||
semester,
|
||||
teacher.id,
|
||||
subject.id)
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = TYPE_NORMAL,
|
||||
value = value,
|
||||
weight = weight,
|
||||
color = colorInt,
|
||||
category = category,
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacher.id,
|
||||
subjectId = subject.id)
|
||||
|
||||
when (grade.getInt("Typ")) {
|
||||
0 -> {
|
||||
@ -98,17 +101,19 @@ class IdziennikWebGrades(override val data: DataIdziennik,
|
||||
}
|
||||
|
||||
val historyObject = Grade(
|
||||
profileId,
|
||||
gradeObject.id * -1,
|
||||
historyItem.get("Kategoria").asString,
|
||||
colorInt,
|
||||
historyItem.get("Uzasadnienie").asString,
|
||||
historyItem.get("Ocena").asString,
|
||||
value,
|
||||
if (value > 0f && countToTheAverage) weight * -1f else 0f,
|
||||
historyItem.get("Semestr").asInt,
|
||||
teacher.id,
|
||||
subject.id)
|
||||
profileId = profileId,
|
||||
id = gradeObject.id * -1,
|
||||
name = historyItem.getString("Ocena") ?: "",
|
||||
type = TYPE_NORMAL,
|
||||
value = value,
|
||||
weight = if (value > 0f && countToTheAverage) weight * -1f else 0f,
|
||||
color = colorInt,
|
||||
category = historyItem.getString("Kategoria"),
|
||||
description = historyItem.getString("Uzasadnienie"),
|
||||
comment = null,
|
||||
semester = historyItem.getInt("Semestr") ?: 1,
|
||||
teacherId = teacher.id,
|
||||
subjectId = subject.id)
|
||||
historyObject.parentId = gradeObject.id
|
||||
|
||||
val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
|
||||
|
@ -13,8 +13,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_YEAR_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getJsonArray
|
||||
@ -54,20 +54,20 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
|
||||
|
||||
if (semester1Proposed != "") {
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
semester1Id,
|
||||
"",
|
||||
-1,
|
||||
"",
|
||||
semester1Value.toString(),
|
||||
semester1Value.toFloat(),
|
||||
0f,
|
||||
1,
|
||||
-1,
|
||||
subjectObject.id
|
||||
).apply {
|
||||
type = TYPE_SEMESTER1_PROPOSED
|
||||
}
|
||||
profileId = profileId,
|
||||
id = semester1Id,
|
||||
name = semester1Value.toString(),
|
||||
type = TYPE_SEMESTER1_PROPOSED,
|
||||
value = semester1Value.toFloat(),
|
||||
weight = 0f,
|
||||
color = -1,
|
||||
category = null,
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = 1,
|
||||
teacherId = -1,
|
||||
subjectId = subjectObject.id
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -82,20 +82,25 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
|
||||
|
||||
if (semester2Proposed != "") {
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
semester2Id,
|
||||
"",
|
||||
-1,
|
||||
"",
|
||||
semester2Value.toString(),
|
||||
semester2Value.toFloat(),
|
||||
0f,
|
||||
2,
|
||||
-1,
|
||||
subjectObject.id
|
||||
).apply {
|
||||
type = TYPE_YEAR_PROPOSED
|
||||
}
|
||||
profileId = profileId,
|
||||
id = semester2Id,
|
||||
name = semester2Value.toString(),
|
||||
type = TYPE_YEAR_PROPOSED,
|
||||
value = semester2Value.toFloat(),
|
||||
weight = 0f,
|
||||
color = -1,
|
||||
category = null,
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = 2,
|
||||
teacherId = -1,
|
||||
subjectId = subjectObject.id
|
||||
)
|
||||
|
||||
val addedDate = if (data.profile.empty)
|
||||
data.profile.dateSemester1Start.inMillis
|
||||
else
|
||||
System.currentTimeMillis()
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -104,7 +109,7 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
|
||||
gradeObject.id,
|
||||
profile.empty,
|
||||
profile.empty,
|
||||
System.currentTimeMillis()
|
||||
addedDate
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOME
|
||||
import pl.szczodrzynski.edziennik.data.api.Regexes
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
|
||||
class IdziennikWebSwitchRegister(override val data: DataIdziennik,
|
||||
val registerId: Int,
|
||||
val onSuccess: () -> Unit
|
||||
) : IdziennikWeb(data, null) {
|
||||
companion object {
|
||||
private const val TAG = "IdziennikWebSwitchRegister"
|
||||
}
|
||||
|
||||
init {
|
||||
val hiddenFields = data.loginStore.getLoginData("hiddenFields", JsonObject())
|
||||
// TODO error checking
|
||||
|
||||
webGet(TAG, IDZIENNIK_WEB_HOME, mapOf(
|
||||
"__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""),
|
||||
"__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""),
|
||||
"__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""),
|
||||
"ctl00\$dxComboUczniowie" to registerId
|
||||
)) { text ->
|
||||
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
|
||||
val registerId = it[1].toIntOrNull() ?: return@let
|
||||
data.webSelectedRegister = registerId
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@ import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import okhttp3.Cookie
|
||||
import pl.szczodrzynski.edziennik.HOUR
|
||||
import pl.szczodrzynski.edziennik.MINUTE
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
@ -69,6 +69,40 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
||||
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
|
||||
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
|
||||
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
|
||||
|
||||
val hiddenFields = JsonObject()
|
||||
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach {
|
||||
hiddenFields[it[1]] = it[2]
|
||||
}
|
||||
data.loginStore.putLoginData("hiddenFields", hiddenFields)
|
||||
|
||||
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
|
||||
val registerId = it[1].toIntOrNull() ?: return@let
|
||||
data.webSelectedRegister = registerId
|
||||
}
|
||||
|
||||
data.profile?.let { profile ->
|
||||
Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also {
|
||||
val number = it[1].toIntOrNull() ?: return@also
|
||||
val luckyNumberObject = LuckyNumber(
|
||||
data.profileId,
|
||||
Date.getToday(),
|
||||
number
|
||||
)
|
||||
|
||||
data.luckyNumberList.add(luckyNumberObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profile.id,
|
||||
Metadata.TYPE_LUCKY_NUMBER,
|
||||
luckyNumberObject.date.value.toLong(),
|
||||
true,
|
||||
profile.empty,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return@run null
|
||||
}?.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
|
@ -191,6 +191,16 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
|
||||
set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; }
|
||||
|
||||
/**
|
||||
* A push device ID, generated by Librus when registering
|
||||
* a FCM token. I don't really know if this has any use,
|
||||
* but it may be worthy to save that ID.
|
||||
*/
|
||||
private var mPushDeviceId: Int? = null
|
||||
var pushDeviceId: Int
|
||||
get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 }
|
||||
set(value) { mPushDeviceId = value; profile?.putStudentData("pushDeviceId", value) ?: return; }
|
||||
|
||||
/* _____ _
|
||||
/ ____| (_)
|
||||
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
|
||||
|
@ -56,9 +56,9 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId)
|
||||
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId, onlyEndpoints)
|
||||
login()
|
||||
}
|
||||
|
||||
@ -180,6 +180,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE,
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING,
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED,
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED,
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED -> {
|
||||
login()
|
||||
@ -198,7 +199,6 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID -> {
|
||||
login()
|
||||
}
|
||||
// TODO PORTAL CAPTCHA
|
||||
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
|
||||
data.timetableNotPublic = true
|
||||
data()
|
||||
@ -207,6 +207,11 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> {
|
||||
data()
|
||||
}
|
||||
ERROR_LIBRUS_API_DEVICE_REGISTERED -> {
|
||||
data.app.config.sync.tokenLibrusList =
|
||||
data.app.config.sync.tokenLibrusList + data.profileId
|
||||
data()
|
||||
}
|
||||
else -> callback.onError(apiError)
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,6 @@ const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030
|
||||
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010
|
||||
const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020
|
||||
const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030
|
||||
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 3040
|
||||
const val ENDPOINT_LIBRUS_MESSAGES_GET = 3040
|
||||
|
||||
val LibrusFeatures = listOf(
|
||||
|
||||
|
@ -44,9 +44,12 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
/*
|
||||
{"Status":"Error","Code":"DeviceRegistered","Message":"This device is alerdy registered.","Resources":{"..":{"Url":"https:\/\/api.librus.pl\/2.0\/Root"}},"Url":"https:\/\/api.librus.pl\/2.0\/ChangeRegister"}*/
|
||||
val error = if (response?.code() == 200) null else
|
||||
json.getString("Code") ?:
|
||||
json.getString("Message") ?:
|
||||
json.getString("Status") ?:
|
||||
response?.parserErrorBody
|
||||
error?.let { code ->
|
||||
when (code) {
|
||||
@ -64,6 +67,8 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
|
||||
"InvalidRequest" -> ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS
|
||||
"Nieprawidłowy węzeł." -> ERROR_LIBRUS_API_INCORRECT_ENDPOINT
|
||||
"NoticeboardProblem" -> ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM
|
||||
"DeviceRegistered" -> ERROR_LIBRUS_API_DEVICE_REGISTERED
|
||||
"Maintenance" -> ERROR_LIBRUS_API_MAINTENANCE
|
||||
else -> ERROR_LIBRUS_API_OTHER
|
||||
}.let { errorCode ->
|
||||
if (errorCode !in ignoreErrors) {
|
||||
@ -115,6 +120,8 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
|
||||
.allowErrorCode(HTTP_FORBIDDEN)
|
||||
.allowErrorCode(HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HTTP_UNAVAILABLE)
|
||||
.allowErrorCode(HTTP_NOT_FOUND)
|
||||
.allowErrorCode(503)
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
|
@ -31,15 +31,15 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
/**
|
||||
@ -81,7 +81,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lessons)
|
||||
LibrusApiLessons(data, lastSync, onSuccess)
|
||||
}
|
||||
// TODO push config
|
||||
ENDPOINT_LIBRUS_API_PUSH_CONFIG -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
|
||||
LibrusApiPushConfig(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_LIBRUS_API_TIMETABLES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||
LibrusApiTimetables(data, lastSync, onSuccess)
|
||||
|
@ -55,13 +55,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
||||
}
|
||||
|
||||
when {
|
||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
||||
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
||||
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
||||
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
|
||||
text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
|
||||
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
|
||||
text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||
text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
|
||||
text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
|
||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
|
||||
else -> null
|
||||
}?.let { errorCode ->
|
||||
data.error(ApiError(tag, errorCode)
|
||||
.withApiResponse(text)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
@ -139,13 +146,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
||||
}
|
||||
|
||||
when {
|
||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
||||
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
||||
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
||||
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
|
||||
text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
|
||||
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
|
||||
text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||
text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
|
||||
text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
|
||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
|
||||
else -> null
|
||||
}?.let { errorCode ->
|
||||
data.error(ApiError(tag, errorCode)
|
||||
.withApiResponse(text)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -44,6 +44,7 @@ open class LibrusPortal(open val data: DataLibrus) {
|
||||
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
|
||||
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
|
||||
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
|
||||
"Unable to refresh the account" -> ERROR_LIBRUS_PORTAL_MAINTENANCE
|
||||
else -> when (json.getString("hint")) {
|
||||
"Error while decoding to JSON" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
|
||||
else -> ERROR_LIBRUS_PORTAL_OTHER
|
||||
@ -97,6 +98,7 @@ open class LibrusPortal(open val data: DataLibrus) {
|
||||
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_GONE)
|
||||
.allowErrorCode(424)
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
|
@ -42,7 +42,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
||||
val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id")
|
||||
val semester = attendance.getInt("Semester") ?: return@forEach
|
||||
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
|
||||
val typeObject = data.attendanceTypes.get(type)
|
||||
val typeObject = data.attendanceTypes[type] ?: null
|
||||
val topic = typeObject?.name ?: ""
|
||||
|
||||
val startTime = data.lessonRanges.get(lessonNo).startTime
|
||||
@ -60,13 +60,13 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
||||
topic,
|
||||
lessonDate,
|
||||
startTime,
|
||||
typeObject.type
|
||||
typeObject?.type ?: Attendance.TYPE_CUSTOM
|
||||
)
|
||||
|
||||
val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach)
|
||||
|
||||
data.attendanceList.add(attendanceObject)
|
||||
if(typeObject.type != Attendance.TYPE_PRESENT) {
|
||||
if(typeObject?.type != Attendance.TYPE_PRESENT) {
|
||||
data.metadataList.add(Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_ATTENDANCE,
|
||||
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
@ -26,23 +27,36 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
|
||||
|
||||
private val nameFormat by lazy { DecimalFormat("#.##") }
|
||||
|
||||
private val types by lazy {
|
||||
mapOf(
|
||||
1 to ("wz" to "wzorowe"),
|
||||
2 to ("bdb" to "bardzo dobre"),
|
||||
3 to ("db" to "dobre"),
|
||||
4 to ("popr" to "poprawne"),
|
||||
5 to ("ndp" to "nieodpowiednie"),
|
||||
6 to ("ng" to "naganne")
|
||||
)
|
||||
}
|
||||
|
||||
init { data.profile?.also { profile ->
|
||||
apiGet(TAG, "BehaviourGrades/Points") { json ->
|
||||
|
||||
if (data.startPointsSemester1 > 0) {
|
||||
val semester1StartGradeObject = Grade(
|
||||
profileId,
|
||||
-101,
|
||||
data.app.getString(R.string.grade_start_points),
|
||||
0xffbdbdbd.toInt(),
|
||||
data.app.getString(R.string.grade_start_points_format, 1),
|
||||
nameFormat.format(data.startPointsSemester1),
|
||||
data.startPointsSemester1.toFloat(),
|
||||
-1f,
|
||||
1,
|
||||
-1,
|
||||
1
|
||||
).apply { type = Grade.TYPE_POINT_SUM }
|
||||
profileId = profileId,
|
||||
id = -101,
|
||||
name = nameFormat.format(data.startPointsSemester1),
|
||||
type = TYPE_POINT_SUM,
|
||||
value = data.startPointsSemester1.toFloat(),
|
||||
weight = 0f,
|
||||
color = 0xffbdbdbd.toInt(),
|
||||
category = data.app.getString(R.string.grade_start_points),
|
||||
description = data.app.getString(R.string.grade_start_points_format, 1),
|
||||
comment = null,
|
||||
semester = 1,
|
||||
teacherId = -1,
|
||||
subjectId = 1
|
||||
)
|
||||
|
||||
data.gradeList.add(semester1StartGradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -57,18 +71,20 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
|
||||
|
||||
if (data.startPointsSemester2 > 0) {
|
||||
val semester2StartGradeObject = Grade(
|
||||
profileId,
|
||||
-102,
|
||||
data.app.getString(R.string.grade_start_points),
|
||||
0xffbdbdbd.toInt(),
|
||||
data.app.getString(R.string.grade_start_points_format, 2),
|
||||
nameFormat.format(data.startPointsSemester2),
|
||||
data.startPointsSemester2.toFloat(),
|
||||
-1f,
|
||||
2,
|
||||
-1,
|
||||
1
|
||||
).apply { type = Grade.TYPE_POINT_SUM }
|
||||
profileId = profileId,
|
||||
id = -102,
|
||||
name = nameFormat.format(data.startPointsSemester2),
|
||||
type = TYPE_POINT_SUM,
|
||||
value = data.startPointsSemester2.toFloat(),
|
||||
weight = -1f,
|
||||
color = 0xffbdbdbd.toInt(),
|
||||
category = data.app.getString(R.string.grade_start_points),
|
||||
description = data.app.getString(R.string.grade_start_points_format, 2),
|
||||
comment = null,
|
||||
semester = 2,
|
||||
teacherId = -1,
|
||||
subjectId = 1
|
||||
)
|
||||
|
||||
data.gradeList.add(semester2StartGradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
@ -90,8 +106,12 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
|
||||
val addedDate = grade.getString("AddDate")?.let { Date.fromIso(it) }
|
||||
?: System.currentTimeMillis()
|
||||
|
||||
val text = grade.getString("Text")
|
||||
val type = grade.getJsonObject("BehaviourGrade")?.getInt("Id")?.let { types[it] }
|
||||
|
||||
val name = when {
|
||||
value != null -> (if (value >= 0) "+" else "") + nameFormat.format(value)
|
||||
type != null -> type.first
|
||||
value != null -> (if (value > 0) "+" else "") + nameFormat.format(value)
|
||||
shortName != null -> shortName
|
||||
else -> return@forEach
|
||||
}
|
||||
@ -110,32 +130,33 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
|
||||
|
||||
val categoryName = category?.text ?: ""
|
||||
|
||||
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
|
||||
if (comments.isNotEmpty()) {
|
||||
data.gradeCategories.singleOrNull {
|
||||
it.type == GradeCategory.TYPE_BEHAVIOUR_COMMENT
|
||||
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
|
||||
}?.text
|
||||
} else null
|
||||
} ?: ""
|
||||
val comments = grade.getJsonArray("Comments")
|
||||
?.asJsonObjectList()
|
||||
?.mapNotNull { comment ->
|
||||
val cId = comment.getLong("Id") ?: return@mapNotNull null
|
||||
data.gradeCategories[cId]?.text
|
||||
} ?: listOf()
|
||||
|
||||
val description = listOfNotNull(type?.second) + comments
|
||||
|
||||
val valueFrom = value ?: category?.valueFrom ?: 0f
|
||||
val valueTo = category?.valueTo ?: 0f
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
categoryName,
|
||||
color,
|
||||
description,
|
||||
name,
|
||||
valueFrom,
|
||||
-1f,
|
||||
semester,
|
||||
teacherId,
|
||||
1
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = TYPE_POINT_SUM,
|
||||
value = valueFrom,
|
||||
weight = -1f,
|
||||
color = color,
|
||||
category = categoryName,
|
||||
description = text ?: description.join(" - "),
|
||||
comment = if (text != null) description.join(" - ") else null,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = 1
|
||||
).apply {
|
||||
type = Grade.TYPE_POINT_SUM
|
||||
valueMax = valueTo
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_DESCRIPTIVE_TEXT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_TEXT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE_TEXT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_TEXT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
@ -53,20 +53,20 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus,
|
||||
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category?.text ?: "",
|
||||
category?.color ?: -1,
|
||||
description,
|
||||
" ",
|
||||
0f,
|
||||
0f,
|
||||
semester,
|
||||
teacherId,
|
||||
subjectId
|
||||
).apply {
|
||||
this.type = type
|
||||
}
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = " ",
|
||||
type = type,
|
||||
value = 0f,
|
||||
weight = 0f,
|
||||
color = category?.color ?: -1,
|
||||
category = category?.text,
|
||||
description = description,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
|
@ -6,7 +6,13 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
@ -54,32 +60,28 @@ class LibrusApiGrades(override val data: DataLibrus,
|
||||
} ?: ""
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category?.text ?: "",
|
||||
category?.color ?: -1,
|
||||
description,
|
||||
name,
|
||||
value,
|
||||
weight,
|
||||
semester,
|
||||
teacherId,
|
||||
subjectId
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = when {
|
||||
grade.getBoolean("IsConstituent") ?: false -> TYPE_NORMAL
|
||||
grade.getBoolean("IsSemester") ?: false -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL
|
||||
grade.getBoolean("IsSemesterProposition") ?: false -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED
|
||||
grade.getBoolean("IsFinal") ?: false -> TYPE_YEAR_FINAL
|
||||
grade.getBoolean("IsFinalProposition") ?: false -> TYPE_YEAR_PROPOSED
|
||||
else -> TYPE_NORMAL
|
||||
},
|
||||
value = value,
|
||||
weight = weight,
|
||||
color = category?.color ?: -1,
|
||||
category = category?.text ?: "",
|
||||
description = description,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
|
||||
when {
|
||||
grade.getBoolean("IsConstituent") ?: false ->
|
||||
gradeObject.type = TYPE_NORMAL
|
||||
grade.getBoolean("IsSemester") ?: false -> // semester final
|
||||
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL
|
||||
grade.getBoolean("IsSemesterProposition") ?: false -> // semester proposed
|
||||
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED
|
||||
grade.getBoolean("IsFinal") ?: false -> // year final
|
||||
gradeObject.type = TYPE_YEAR_FINAL
|
||||
grade.getBoolean("IsFinalProposition") ?: false -> // year final
|
||||
gradeObject.type = TYPE_YEAR_PROPOSED
|
||||
}
|
||||
|
||||
grade.getJsonObject("Improvement")?.also {
|
||||
val historicalId = it.getLong("Id")
|
||||
data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade ->
|
||||
|
@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_POINT_AVG
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
@ -44,19 +44,20 @@ class LibrusApiPointGrades(override val data: DataLibrus,
|
||||
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category?.text ?: "",
|
||||
category?.color ?: -1,
|
||||
"",
|
||||
name,
|
||||
value,
|
||||
category?.weight ?: 0f,
|
||||
semester,
|
||||
teacherId,
|
||||
subjectId
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = TYPE_POINT_AVG,
|
||||
value = value,
|
||||
weight = category?.weight ?: 0f,
|
||||
color = category?.color ?: -1,
|
||||
category = category?.text ?: "",
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
).apply {
|
||||
type = TYPE_POINT_AVG
|
||||
valueMax = category?.valueTo ?: 0f
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PUSH_CONFIG
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getJsonObject
|
||||
|
||||
class LibrusApiPushConfig(override val data: DataLibrus,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : LibrusApi(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "LibrusApiPushConfig"
|
||||
}
|
||||
|
||||
init { data.app.config.sync.tokenLibrus?.also { tokenLibrus ->
|
||||
apiGet(TAG, "ChangeRegister", payload = JsonObject(
|
||||
"provider" to "FCM",
|
||||
"device" to tokenLibrus,
|
||||
"sendPush" to "1",
|
||||
"appVersion" to 4
|
||||
)) { json ->
|
||||
json.getJsonObject("ChangeRegister")?.getInt("Id")?.let { data.pushDeviceId = it }
|
||||
|
||||
// sync always: this endpoint has .shouldSync set
|
||||
data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS)
|
||||
data.app.config.sync.tokenLibrusList =
|
||||
data.app.config.sync.tokenLibrusList + profileId
|
||||
onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG)
|
||||
}
|
||||
} ?: onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) }
|
||||
}
|
@ -8,8 +8,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
|
||||
class LibrusApiTemplate(override val data: DataLibrus,
|
||||
val onSuccess: () -> Unit
|
||||
) : LibrusApi(data, null) {
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : LibrusApi(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "LibrusApi"
|
||||
}
|
||||
@ -18,7 +19,7 @@ class LibrusApiTemplate(override val data: DataLibrus,
|
||||
/*apiGet(TAG, "") { json ->
|
||||
|
||||
data.setSyncNext(ENDPOINT_LIBRUS_API_, SYNC_ALWAYS)
|
||||
onSuccess()
|
||||
onSuccess(ENDPOINT_LIBRUS_API_)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_DESCRIPTIVE
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
@ -48,20 +48,20 @@ class LibrusApiTextGrades(override val data: DataLibrus,
|
||||
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category?.text ?: "",
|
||||
category?.color ?: -1,
|
||||
description,
|
||||
name,
|
||||
0f,
|
||||
0f,
|
||||
semester,
|
||||
teacherId,
|
||||
subjectId
|
||||
).apply {
|
||||
type = TYPE_DESCRIPTIVE
|
||||
}
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = TYPE_DESCRIPTIVE,
|
||||
value = 0f,
|
||||
weight = 0f,
|
||||
color = category?.color ?: -1,
|
||||
category = category?.text ?: "",
|
||||
description = description,
|
||||
comment = grade.getString("Phrase") /* whatever it is */,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
|
@ -111,7 +111,7 @@ class LibrusMessagesGetList(override val data: DataLibrus,
|
||||
|
||||
data.messageIgnoreList.add(messageObject)
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
data.metadataList.add(Metadata(
|
||||
data.setSeenMetadataList.add(Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_MESSAGE,
|
||||
id,
|
||||
|
@ -29,7 +29,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
||||
init { data.profile?.also { profile ->
|
||||
synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf(
|
||||
"dataOd" to
|
||||
if (!data.profile.empty)
|
||||
if (profile.empty)
|
||||
profile.getSemesterStart(1).stringY_m_d
|
||||
else
|
||||
Date.getToday().stringY_m_d,
|
||||
@ -54,7 +54,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
||||
val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id
|
||||
?: -1
|
||||
val topic = elements[2].text().trim()
|
||||
val addedDate = Date.fromY_m_d(elements[4].text().trim()).inMillis
|
||||
val addedDate = Date.fromY_m_d(elements[4].text().trim())
|
||||
val eventDate = Date.fromY_m_d(elements[6].text().trim())
|
||||
val id = "/podglad/([0-9]+)'".toRegex().find(
|
||||
elements[9].select("input").attr("onclick")
|
||||
@ -63,10 +63,25 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
||||
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
|
||||
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
val moreInfo = graphElements[2 * i + 1].select("td[title]")
|
||||
.attr("title").trim()
|
||||
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
|
||||
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim()
|
||||
/*val moreInfo = graphElements[2 * i + 1].select("td[title]")
|
||||
.attr("title").trim()*/
|
||||
|
||||
var description = ""
|
||||
|
||||
graphElements.forEach { graphEl ->
|
||||
graphEl.select("td[title]")?.also {
|
||||
val title = it.attr("title")
|
||||
val r = "Temat: (.*?)<br.?/>Data udostępnienia: (.*?)<br.?/>Termin wykonania: (.*?)<br.?/>Treść: (.*)"
|
||||
.toRegex(RegexOption.DOT_MATCHES_ALL).find(title) ?: return@forEach
|
||||
val gTopic = r[1].trim()
|
||||
val gAddedDate = Date.fromY_m_d(r[2].trim())
|
||||
val gEventDate = Date.fromY_m_d(r[3].trim())
|
||||
if (gTopic == topic && gAddedDate == addedDate && gEventDate == eventDate) {
|
||||
description = r[4].replace("<br.?/>".toRegex(), "\n").trim()
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val seen = when (profile.empty) {
|
||||
true -> true
|
||||
@ -94,7 +109,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
||||
id,
|
||||
seen,
|
||||
seen,
|
||||
addedDate
|
||||
addedDate.inMillis
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
return@portalGet
|
||||
}
|
||||
|
||||
val isParent = account.getString("group") == "parent"
|
||||
|
||||
val id = account.getInt("id") ?: continue
|
||||
val login = account.getString("login") ?: continue
|
||||
val token = account.getString("accessToken") ?: continue
|
||||
@ -69,7 +71,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
data.portalEmail,
|
||||
studentNameLong,
|
||||
studentNameShort,
|
||||
null
|
||||
if (isParent) studentNameLong else null /* temporarily - there is no parent name provided, only the type */
|
||||
).apply {
|
||||
studentData["accountId"] = id
|
||||
studentData["accountLogin"] = login
|
||||
|
@ -137,6 +137,7 @@ class LibrusLoginApi {
|
||||
"librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR
|
||||
"librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED
|
||||
"invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
|
||||
"invalid_request" -> ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST
|
||||
else -> ERROR_LOGIN_LIBRUS_API_OTHER
|
||||
}.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
|
@ -12,7 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
import java.net.HttpURLConnection.*
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@ -63,23 +63,31 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.withClient(data.app.httpLazy)
|
||||
.callback(object : TextCallbackHandler() {
|
||||
override fun onSuccess(json: String, response: Response) {
|
||||
override fun onSuccess(text: String, response: Response) {
|
||||
val location = response.headers().get("Location")
|
||||
if (location != null) {
|
||||
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||
if (authMatcher.find()) {
|
||||
accessToken(authMatcher.group(1), null)
|
||||
} else {
|
||||
authorize(location)
|
||||
when {
|
||||
authMatcher.find() -> {
|
||||
accessToken(authMatcher.group(1), null)
|
||||
}
|
||||
location.contains("rejected_client") -> {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID)
|
||||
.withResponse(response)
|
||||
.withApiResponse("Location: $location\n$text"))
|
||||
}
|
||||
else -> {
|
||||
authorize(location)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(json)
|
||||
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text)
|
||||
if (csrfMatcher.find()) {
|
||||
login(csrfMatcher.group(1))
|
||||
} else {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
.withApiResponse(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,6 +120,8 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
it.addParameter("g-recaptcha-response", recaptchaCode)
|
||||
}
|
||||
.addHeader("X-CSRF-TOKEN", csrfToken)
|
||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HTTP_FORBIDDEN)
|
||||
.contentType(MediaTypeUtils.APPLICATION_JSON)
|
||||
.post()
|
||||
.callback(object : JsonCallbackHandler() {
|
||||
@ -134,28 +144,32 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
val error = if (response.code() == 200) null else
|
||||
json.getJsonArray("errors")?.getString(0)
|
||||
error?.let { code ->
|
||||
when {
|
||||
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
||||
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
// this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set
|
||||
code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL
|
||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
|
||||
}.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
}
|
||||
if (json.getBoolean("captchaRequired") == true) {
|
||||
data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
return
|
||||
}
|
||||
if (json.get("errors") != null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
return
|
||||
}
|
||||
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response, throwable: Throwable) {
|
||||
if (response.code() == 403 || response.code() == 401) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
return
|
||||
}
|
||||
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
@ -165,7 +179,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private var refreshTokenFailed = false
|
||||
private fun accessToken(code: String?, refreshToken: String?) {
|
||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL}")
|
||||
|
||||
@ -183,7 +196,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
return
|
||||
}
|
||||
val error = if (response?.code() == 200) null else
|
||||
json.getString("hint")
|
||||
json.getString("hint") ?: json.getString("error")
|
||||
error?.let { code ->
|
||||
when (code) {
|
||||
"Authorization code has expired" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED
|
||||
@ -194,11 +207,9 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
"Check the `code` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE
|
||||
"Check the `refresh_token` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH
|
||||
"Check the `redirect_uri` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT
|
||||
else -> when (json.getString("error")) {
|
||||
"unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT
|
||||
"invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID
|
||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER
|
||||
}
|
||||
"unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT
|
||||
"invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID
|
||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER
|
||||
}.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
.withApiResponse(json)
|
||||
|
@ -56,9 +56,9 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId)
|
||||
data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId, onlyEndpoints)
|
||||
login()
|
||||
}
|
||||
|
||||
|
@ -29,15 +29,15 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_MOBIDZIENNIK_API_MAIN -> {
|
||||
|
@ -26,6 +26,23 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
||||
val profile
|
||||
get() = data.profile
|
||||
|
||||
/* TODO
|
||||
add error handling:
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div id="content">
|
||||
<a name="tresc"></a>
|
||||
<h1>Ważna informacja!</h1>
|
||||
|
||||
<div class="okienko_informacyjne">
|
||||
Korzystasz z hasła, które zostało wygenerowane automatycznie.<hr/>
|
||||
Pamiętaj, aby je zmienić oraz uzupełnić dane w swoim profilu.<br/><br/>
|
||||
Obie te operacje można wykonać uruchamiając opcję
|
||||
<a href="https://poznan.mobidziennik.pl/dziennik/edytujprofil" title="Edycja profilu, możliwość zmiany hasła">Moje konto->Edycja profilu</a>.
|
||||
</div> </div>
|
||||
|
||||
*/
|
||||
|
||||
fun webGet(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
@ -65,6 +82,12 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
||||
return
|
||||
}
|
||||
|
||||
if (text.contains("<h2>Problemy z wydajnością</h2>")) {
|
||||
data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
onSuccess(text)
|
||||
} catch (e: Exception) {
|
||||
|
@ -7,7 +7,13 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
|
||||
class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
|
||||
@ -61,18 +67,23 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
|
||||
}
|
||||
|
||||
val gradeObject = Grade(
|
||||
data.profileId,
|
||||
id,
|
||||
category,
|
||||
color,
|
||||
description,
|
||||
name,
|
||||
value,
|
||||
weight,
|
||||
semester,
|
||||
teacherId,
|
||||
subjectId)
|
||||
gradeObject.type = type
|
||||
profileId = data.profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = type,
|
||||
value = value,
|
||||
weight = weight,
|
||||
color = color,
|
||||
category = category,
|
||||
description = description,
|
||||
comment = null,
|
||||
semester = semester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId)
|
||||
|
||||
if (data.profile?.empty == true) {
|
||||
addedDate = data.profile.dateSemester1Start.inMillis
|
||||
}
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(
|
||||
|
@ -118,7 +118,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik,
|
||||
|
||||
// this needs to be at the end
|
||||
message.apply {
|
||||
this.body = body.html().replace("\n", "<br>")
|
||||
this.body = body.html()
|
||||
|
||||
clearAttachments()
|
||||
content.select("ul li").map { it.select("a").first() }.forEach {
|
||||
|
@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_GRADES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.fixWhiteSpaces
|
||||
@ -112,17 +113,19 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
|
||||
}
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
gradeId,
|
||||
gradeCategory,
|
||||
gradeColor,
|
||||
"NLDŚR, $gradeDescription",
|
||||
gradeName,
|
||||
gradeValue,
|
||||
0f,
|
||||
gradeSemester,
|
||||
teacherId,
|
||||
subjectId
|
||||
profileId = profileId,
|
||||
id = gradeId,
|
||||
name = gradeName,
|
||||
type = TYPE_NORMAL,
|
||||
value = gradeValue,
|
||||
weight = 0f,
|
||||
color = gradeColor,
|
||||
category = gradeCategory,
|
||||
description = "NLDŚR, $gradeDescription",
|
||||
comment = null,
|
||||
semester = gradeSemester,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
|
||||
gradeObject.classAverage = gradeClassAverage
|
||||
|
@ -32,15 +32,15 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
|
||||
|
||||
val doc = Jsoup.parse(text)
|
||||
|
||||
val listElement = doc.getElementsByClass("spis").first()
|
||||
val listElement = doc.getElementsByClass("spis")?.first()
|
||||
if (listElement == null) {
|
||||
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL, 7*DAY)
|
||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL)
|
||||
return@webGet
|
||||
}
|
||||
val list = listElement.getElementsByClass("podswietl")
|
||||
for (item in list) {
|
||||
val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: continue
|
||||
list?.forEach { item ->
|
||||
val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: return@forEach
|
||||
|
||||
val subjectEl = item.select("td:eq(0) div").first()
|
||||
val subject = subjectEl.text()
|
||||
|
@ -36,9 +36,9 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik,
|
||||
|
||||
val doc = Jsoup.parse(text)
|
||||
|
||||
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl")
|
||||
for (item in list) {
|
||||
val id = item.attr("rel").toLongOrNull() ?: continue
|
||||
val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
|
||||
list?.forEach { item ->
|
||||
val id = item.attr("rel").toLongOrNull() ?: return@forEach
|
||||
|
||||
val subjectEl = item.select("td:eq(0)").first()
|
||||
var hasAttachments = false
|
||||
|
@ -40,9 +40,9 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik,
|
||||
|
||||
val doc = Jsoup.parse(text)
|
||||
|
||||
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl")
|
||||
for (item in list) {
|
||||
val id = item.attr("rel").toLongOrNull() ?: continue
|
||||
val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
|
||||
list?.forEach { item ->
|
||||
val id = item.attr("rel").toLongOrNull() ?: return@forEach
|
||||
|
||||
val subjectEl = item.select("td:eq(0)").first()
|
||||
var hasAttachments = false
|
||||
|
@ -51,9 +51,9 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId)
|
||||
data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId, onlyEndpoints)
|
||||
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
|
||||
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
|
||||
TemplateLogin(data) {
|
||||
|
@ -32,15 +32,15 @@ class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_TEMPLATE_WEB_SAMPLE -> {
|
||||
|
@ -10,7 +10,10 @@ import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_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,6 +29,25 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// during the first sync `profile.studentClassName` is already set
|
||||
if (teamList.values().none { it.type == Team.TYPE_CLASS }) {
|
||||
profile?.studentClassName?.also { name ->
|
||||
val id = Utils.crc16(name.toByteArray()).toLong()
|
||||
|
||||
val teamObject = Team(
|
||||
profileId,
|
||||
id,
|
||||
name,
|
||||
Team.TYPE_CLASS,
|
||||
"$schoolName:$name",
|
||||
-1
|
||||
)
|
||||
teamList.put(id, teamObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun generateUserCode() = "$schoolName:$studentId"
|
||||
|
||||
/**
|
||||
@ -188,11 +210,11 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
"SZ9" -> "http://hack.szkolny.eu"
|
||||
else -> null
|
||||
}
|
||||
return if (url != null) "$url/$symbol" else loginStore.getLoginData("apiUrl", null)
|
||||
return if (url != null) "$url/$symbol/" else loginStore.getLoginData("apiUrl", null)
|
||||
}
|
||||
|
||||
val fullApiUrl: String?
|
||||
get() {
|
||||
return "$apiUrl/$schoolSymbol"
|
||||
return "$apiUrl$schoolSymbol/"
|
||||
}
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId)
|
||||
data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId, onlyEndpoints)
|
||||
login()
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.models.Feature
|
||||
|
||||
const val ENDPOINT_VULCAN_API_UPDATE_SEMESTER = 1000
|
||||
const val ENDPOINT_VULCAN_API_PUSH_CONFIG = 1005
|
||||
const val ENDPOINT_VULCAN_API_DICTIONARIES = 1010
|
||||
const val ENDPOINT_VULCAN_API_TIMETABLE = 1020
|
||||
const val ENDPOINT_VULCAN_API_EVENTS = 1030
|
||||
@ -53,6 +54,13 @@ val VulcanFeatures = listOf(
|
||||
ENDPOINT_VULCAN_API_MESSAGES_SENT to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)),
|
||||
|
||||
// push config
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
|
||||
ENDPOINT_VULCAN_API_PUSH_CONFIG to LOGIN_METHOD_VULCAN_API
|
||||
), listOf(LOGIN_METHOD_VULCAN_API)).withShouldSync { data ->
|
||||
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
|
||||
},
|
||||
|
||||
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
|
||||
|
@ -13,8 +13,6 @@ import io.github.wulkanowy.signer.android.signContent
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection
|
||||
import java.util.*
|
||||
@ -38,26 +36,10 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
baseUrl: Boolean = false,
|
||||
onSuccess: (json: JsonObject, response: Response?) -> Unit
|
||||
) {
|
||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}/$endpoint"
|
||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
||||
|
||||
d(tag, "Request: Vulcan/Api - $url")
|
||||
|
||||
if (data.teamList.size() == 0) {
|
||||
data.profile?.studentClassName?.also { name ->
|
||||
val id = Utils.crc16(name.toByteArray()).toLong()
|
||||
|
||||
val teamObject = Team(
|
||||
profileId,
|
||||
id,
|
||||
name,
|
||||
Team.TYPE_CLASS,
|
||||
"${data.schoolName}:$name",
|
||||
-1
|
||||
)
|
||||
data.teamList.put(id, teamObject)
|
||||
}
|
||||
}
|
||||
|
||||
val finalPayload = JsonObject()
|
||||
parameters.map { (name, value) ->
|
||||
when (value) {
|
||||
@ -68,6 +50,7 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
is Long -> finalPayload.addProperty(name, value)
|
||||
is Float -> finalPayload.addProperty(name, value)
|
||||
is Char -> finalPayload.addProperty(name, value)
|
||||
is Boolean -> finalPayload.addProperty(name, value)
|
||||
}
|
||||
}
|
||||
finalPayload.addProperty("RemoteMobileTimeKey", System.currentTimeMillis() / 1000)
|
||||
|
@ -27,21 +27,25 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
useEndpoint(data.targetEndpointIds.firstKey()) { endpointId ->
|
||||
data.targetEndpointIds.remove(endpointId)
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, onSuccess: (endpointId: Int) -> Unit) {
|
||||
val lastSync = data.targetEndpointIds[endpointId]
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_VULCAN_API_UPDATE_SEMESTER -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
|
||||
VulcanApiUpdateSemester(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_API_PUSH_CONFIG -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
|
||||
VulcanApiPushConfig(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_API_DICTIONARIES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_dictionaries)
|
||||
VulcanApiDictionaries(data, lastSync, onSuccess)
|
||||
|
@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import java.text.DecimalFormat
|
||||
@ -88,17 +89,19 @@ class VulcanApiGrades(override val data: DataVulcan,
|
||||
}.toInt()
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
category,
|
||||
color,
|
||||
finalDescription,
|
||||
name,
|
||||
value ?: 0.0f,
|
||||
weight,
|
||||
data.studentSemesterNumber,
|
||||
teacherId,
|
||||
subjectId
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = TYPE_NORMAL,
|
||||
value = value ?: 0.0f,
|
||||
weight = weight,
|
||||
color = color,
|
||||
category = category,
|
||||
description = finalDescription,
|
||||
comment = null,
|
||||
semester = data.studentSemesterNumber,
|
||||
teacherId = teacherId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
|
@ -8,6 +8,10 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_GRADES_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.getJsonArray
|
||||
import pl.szczodrzynski.edziennik.getJsonObject
|
||||
@ -53,23 +57,25 @@ class VulcanApiProposedGrades(override val data: DataVulcan,
|
||||
val color = Utils.getVulcanGradeColor(name)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
"",
|
||||
color,
|
||||
"",
|
||||
name,
|
||||
value,
|
||||
0f,
|
||||
data.studentSemesterNumber,
|
||||
-1,
|
||||
subjectId
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = name,
|
||||
type = if (data.studentSemesterNumber == 1) {
|
||||
if (isFinal) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER1_PROPOSED
|
||||
} else {
|
||||
if (isFinal) TYPE_SEMESTER2_FINAL else TYPE_SEMESTER2_PROPOSED
|
||||
},
|
||||
value = value,
|
||||
weight = 0f,
|
||||
color = color,
|
||||
category = "",
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = data.studentSemesterNumber,
|
||||
teacherId = -1,
|
||||
subjectId = subjectId
|
||||
)
|
||||
if (data.studentSemesterNumber == 1) {
|
||||
gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER1_PROPOSED
|
||||
} else {
|
||||
gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER2_FINAL else Grade.TYPE_SEMESTER2_PROPOSED
|
||||
}
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(Metadata(
|
||||
profileId,
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_PUSH
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_PUSH_CONFIG
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanApiPushConfig(override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanApi(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanApiPushConfig"
|
||||
}
|
||||
|
||||
init { data.app.config.sync.tokenVulcan?.also { tokenVulcan ->
|
||||
apiGet(TAG, VULCAN_API_ENDPOINT_PUSH, parameters = mapOf(
|
||||
"Token" to tokenVulcan,
|
||||
"IdUczen" to data.studentId,
|
||||
"PushOcena" to true,
|
||||
"PushFrekwencja" to true,
|
||||
"PushUwaga" to true,
|
||||
"PushWiadomosc" to true
|
||||
)) { _, _ ->
|
||||
// sync always: this endpoint has .shouldSync set
|
||||
data.setSyncNext(ENDPOINT_VULCAN_API_PUSH_CONFIG, SYNC_ALWAYS)
|
||||
data.app.config.sync.tokenVulcanList =
|
||||
data.app.config.sync.tokenVulcanList + profileId
|
||||
onSuccess(ENDPOINT_VULCAN_API_PUSH_CONFIG)
|
||||
}
|
||||
} ?: onSuccess(ENDPOINT_VULCAN_API_PUSH_CONFIG) }
|
||||
}
|
@ -86,31 +86,36 @@ class VulcanApiTimetable(override val data: DataVulcan,
|
||||
data.teamList[id] = team
|
||||
}
|
||||
team.id
|
||||
} ?: data.studentClassId.toLong()
|
||||
} ?: data.teamClass?.id ?: -1
|
||||
|
||||
val subjectId = lesson.getLong("IdPrzedmiot")?.let {
|
||||
when (it) {
|
||||
0L -> {
|
||||
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
|
||||
val subjectId = lesson.getLong("IdPrzedmiot").let { id ->
|
||||
// get the specified subject name
|
||||
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
|
||||
|
||||
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
|
||||
?: {
|
||||
/**
|
||||
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
|
||||
*/
|
||||
|
||||
val subjectObject = Subject(
|
||||
profileId,
|
||||
-1 * crc16(subjectName.toByteArray()).toLong(),
|
||||
subjectName,
|
||||
subjectName
|
||||
)
|
||||
data.subjectList.put(subjectObject.id, subjectObject)
|
||||
subjectObject.id
|
||||
}.invoke()
|
||||
}
|
||||
else -> it
|
||||
val condition = when (id) {
|
||||
// "special" subject - e.g. one time classes, a trip, etc.
|
||||
0L -> { subject: Subject -> subject.longName == subjectName }
|
||||
// normal subject, check if it exists
|
||||
else -> { subject: Subject -> subject.id == id }
|
||||
}
|
||||
|
||||
data.subjectList.singleOrNull(condition)?.id ?: {
|
||||
/**
|
||||
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
|
||||
*/
|
||||
|
||||
val subjectObject = Subject(
|
||||
profileId,
|
||||
if (id == null || id == 0L)
|
||||
-1 * crc16(subjectName.toByteArray()).toLong()
|
||||
else
|
||||
id,
|
||||
subjectName,
|
||||
subjectName
|
||||
)
|
||||
data.subjectList.put(subjectObject.id, subjectObject)
|
||||
subjectObject.id
|
||||
}()
|
||||
}
|
||||
|
||||
val lessonObject = Lesson(profileId, -1).apply {
|
||||
|
@ -137,7 +137,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url("${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
|
||||
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
|
||||
.userAgent(VULCAN_API_USER_AGENT)
|
||||
.addHeader("RequestMobileType", "RegisterDevice")
|
||||
.addParameter("PIN", data.apiPin)
|
||||
|
@ -5,13 +5,13 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
|
||||
interface EdziennikInterface {
|
||||
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
|
||||
fun sync(featureIds: List<Int>, viewId: Int? = null, onlyEndpoints: List<Int>? = null, arguments: JsonObject? = null)
|
||||
fun getMessage(message: MessageFull)
|
||||
fun sendMessage(recipients: List<Teacher>, subject: String, text: String)
|
||||
fun markAllAnnouncementsAsRead()
|
||||
|
@ -12,10 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||
@ -28,7 +25,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
import pl.szczodrzynski.edziennik.md5
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
@ -80,7 +76,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
withContext(Dispatchers.Default) { block() }
|
||||
}
|
||||
catch (e: Exception) {
|
||||
errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show()
|
||||
errorSnackbar.addError(e.toApiError(TAG)).show()
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -91,7 +87,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
catch (e: Exception) {
|
||||
ErrorDetailsDialog(
|
||||
activity,
|
||||
listOf(ApiError.fromThrowable(TAG, e)),
|
||||
listOf(e.toApiError(TAG)),
|
||||
R.string.error_occured
|
||||
)
|
||||
null
|
||||
@ -160,7 +156,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
|
||||
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>, lastSyncTime: Long): List<EventFull> {
|
||||
val teams = app.db.teamDao().allNow
|
||||
|
||||
val response = api.serverSync(ServerSyncRequest(
|
||||
@ -185,19 +181,26 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
}
|
||||
}
|
||||
},
|
||||
lastSync = lastSyncTime,
|
||||
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
val (events, hasBrowsers) = parseResponse(response)
|
||||
|
||||
val events = mutableListOf<EventFull>()
|
||||
hasBrowsers?.let {
|
||||
app.config.sync.webPushEnabled = it
|
||||
}
|
||||
|
||||
response.body()?.data?.events?.forEach { event ->
|
||||
val eventList = mutableListOf<EventFull>()
|
||||
|
||||
events.forEach { event ->
|
||||
// skip blacklisted events
|
||||
if (event.id in blacklistedIds)
|
||||
return@forEach
|
||||
// create the event for every matching team and profile
|
||||
teams.filter { it.code == event.teamCode }.onEach { team ->
|
||||
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
|
||||
|
||||
events.add(EventFull(event).apply {
|
||||
eventList += EventFull(event).apply {
|
||||
profileId = team.profileId
|
||||
teamId = team.id
|
||||
addedManually = true
|
||||
@ -205,11 +208,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
notified = profile.empty
|
||||
|
||||
if (profile.userCode == event.sharedBy) sharedBy = "self"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
return eventList
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -253,9 +256,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
browserId = browserId,
|
||||
pairToken = pairToken
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
return parseResponse(response).browsers
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -265,9 +267,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
device = getDevice(),
|
||||
action = "listBrowsers"
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
return parseResponse(response).browsers
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -278,9 +279,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
action = "unpairBrowser",
|
||||
browserId = browserId
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
return parseResponse(response).browsers
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -307,9 +307,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
@Throws(Exception::class)
|
||||
fun getUpdate(channel: String): List<Update> {
|
||||
val response = api.updates(channel).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response.body()?.data ?: emptyList()
|
||||
return parseResponse(response)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -321,8 +319,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
targetDeviceId = targetDeviceId,
|
||||
text = text
|
||||
)).execute()
|
||||
val data = parseResponse(response)
|
||||
|
||||
return data.message
|
||||
return parseResponse(response).message
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,6 @@ object Signing {
|
||||
|
||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||
return "$param1.MTIzNDU2Nzg5MDfKS5agyJ===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MDurcz1Rjg===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ data class ServerSyncRequest(
|
||||
val userCodes: List<String>,
|
||||
val users: List<User>? = null,
|
||||
|
||||
val lastSync: Long,
|
||||
|
||||
val notifications: List<Notification>? = null
|
||||
) {
|
||||
data class User(
|
||||
|
@ -6,4 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
|
||||
data class ServerSyncResponse(val events: List<EventFull>)
|
||||
data class ServerSyncResponse(
|
||||
val events: List<EventFull>,
|
||||
val hasBrowsers: Boolean? = null
|
||||
)
|
||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
|
||||
companion object {
|
||||
@ -24,26 +25,27 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
|
||||
*
|
||||
* @return a number of events inserted to DB, possibly needing a notification
|
||||
*/
|
||||
fun run(): Int {
|
||||
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
||||
if (profiles.isNotEmpty()) {
|
||||
val blacklistedIds = app.db.eventDao().blacklistedIds;
|
||||
val events = api.getEvents(profiles, notifications, blacklistedIds)
|
||||
fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int {
|
||||
val blacklistedIds = app.db.eventDao().blacklistedIds
|
||||
val events = api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime)
|
||||
|
||||
if (events.isNotEmpty()) {
|
||||
app.db.metadataDao().addAllIgnore(events.map { event ->
|
||||
Metadata(
|
||||
event.profileId,
|
||||
Metadata.TYPE_EVENT,
|
||||
event.id,
|
||||
event.seen,
|
||||
event.notified,
|
||||
event.addedDate
|
||||
)
|
||||
})
|
||||
return app.db.eventDao().addAll(events).size
|
||||
}
|
||||
app.config.sync.lastAppSync = System.currentTimeMillis()
|
||||
|
||||
if (events.isNotEmpty()) {
|
||||
val today = Date.getToday()
|
||||
app.db.metadataDao().addAllIgnore(events.map { event ->
|
||||
val isPast = event.eventDate < today
|
||||
Metadata(
|
||||
event.profileId,
|
||||
Metadata.TYPE_EVENT,
|
||||
event.id,
|
||||
isPast || markAsSeen || event.seen,
|
||||
isPast || markAsSeen || event.notified,
|
||||
event.addedDate
|
||||
)
|
||||
})
|
||||
return app.db.eventDao().addAll(events).size
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ abstract class IApiTask(open val profileId: Int) {
|
||||
|
||||
companion object {
|
||||
fun enqueueAll(context: Context, tasks: List<IApiTask>) {
|
||||
if (tasks.isEmpty())
|
||||
return
|
||||
Intent(context, ApiService::class.java).let {
|
||||
if (SDK_INT >= O)
|
||||
context.startForegroundService(it)
|
||||
|
@ -10,6 +10,7 @@ import androidx.core.util.forEach
|
||||
import androidx.core.util.set
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification as AppNotification
|
||||
|
||||
class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
@ -17,22 +18,34 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
private const val TAG = "PostNotifications"
|
||||
}
|
||||
|
||||
/*public boolean shouldBeQuiet() {
|
||||
long now = Time.getNow().getInMillis();
|
||||
long start = app.config.getSync().getQuietHoursStart();
|
||||
long end = app.config.getSync().getQuietHoursEnd();
|
||||
private val quiet by lazy { shouldBeQuiet() }
|
||||
fun shouldBeQuiet(): Boolean {
|
||||
if (!app.config.sync.quietHoursEnabled)
|
||||
return false
|
||||
val now = Time.getNow().value
|
||||
val start = app.config.sync.quietHoursStart?.value ?: return false
|
||||
var end = app.config.sync.quietHoursEnd?.value ?: return false
|
||||
if (start > end) {
|
||||
end += 1000 * 60 * 60 * 24;
|
||||
//Log.d(TAG, "Night passing");
|
||||
// the range spans between two days
|
||||
end += 240000
|
||||
}
|
||||
if (start > now) {
|
||||
now += 1000 * 60 * 60 * 24;
|
||||
//Log.d(TAG, "Now is smaller");
|
||||
}
|
||||
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
|
||||
return start > 0 && now >= start && now <= end;
|
||||
}*/
|
||||
fun shouldBeQuiet() = false
|
||||
return now in start..end || now+240000 in start..end
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addDefaults(): NotificationCompat.Builder {
|
||||
return this.setColor(0xff2196f3.toInt())
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(if (quiet) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MAX)
|
||||
.also {
|
||||
if (quiet) {
|
||||
it.setSound(null)
|
||||
it.setVibrate(longArrayOf())
|
||||
}
|
||||
else
|
||||
it.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
}
|
||||
.setGroup(if (quiet) app.notificationChannelsManager.dataQuiet.key else app.notificationChannelsManager.data.key)
|
||||
}
|
||||
|
||||
private fun buildSummaryText(summaryCounts: SparseIntArray): CharSequence {
|
||||
val summaryTexts = mutableListOf<String>()
|
||||
@ -108,11 +121,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
it.addLine(line)
|
||||
}
|
||||
})
|
||||
.setColor(0xff2196f3.toInt())
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.addDefaults()
|
||||
.setContentIntent(summaryIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
@ -131,11 +140,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
.setStyle(NotificationCompat.BigTextStyle()
|
||||
.bigText(it.text))
|
||||
.setWhen(it.addedDate)
|
||||
.setColor(0xff2196f3.toInt())
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.addDefaults()
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||
.setContentIntent(it.getPendingIntent(app))
|
||||
.setAutoCancel(true)
|
||||
@ -155,11 +160,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
.setContentText(buildSummaryText(summaryCounts))
|
||||
.setTicker(newNotificationsText)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setColor(0xff2196f3.toInt())
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.addDefaults()
|
||||
.setGroupSummary(true)
|
||||
.setContentIntent(summaryIntent)
|
||||
.setAutoCancel(true)
|
||||
|
@ -31,19 +31,37 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
|
||||
val notifications = Notifications(app, notificationList, profiles)
|
||||
notifications.run()
|
||||
|
||||
val shouldAppSync = notificationList.isNotEmpty() || (System.currentTimeMillis() - app.config.lastAppSync > 24*HOUR*1000)
|
||||
// do an AppSync every 24 hours, or if WebPush has a notification
|
||||
val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
||||
// App Sync conditions:
|
||||
// - every 24 hours && any profile is registered
|
||||
// - if there are new notifications && any browser is paired
|
||||
val shouldAppSync =
|
||||
System.currentTimeMillis() - app.config.sync.lastAppSync > 24*HOUR*1000
|
||||
&& appSyncProfiles.isNotEmpty()
|
||||
|| notificationList.isNotEmpty()
|
||||
&& app.config.sync.webPushEnabled
|
||||
|
||||
if (shouldAppSync) {
|
||||
// send notifications to web push, get shared events
|
||||
val addedEvents = AppSync(app, notificationList, profiles, api).run()
|
||||
val addedEvents = AppSync(app, notificationList, appSyncProfiles, api).run(app.config.sync.lastAppSync)
|
||||
if (addedEvents > 0) {
|
||||
// create notifications for shared events (not present before app sync)
|
||||
notifications.sharedEventNotifications()
|
||||
}
|
||||
app.config.lastAppSync = System.currentTimeMillis()
|
||||
}
|
||||
d(TAG, "Created ${notificationList.count()} notifications.")
|
||||
|
||||
// filter notifications
|
||||
notificationList
|
||||
.mapNotNull { it.profileId }
|
||||
.distinct()
|
||||
.map { app.config.getFor(it).sync.notificationFilter }
|
||||
.forEach { filter ->
|
||||
filter.forEach { type ->
|
||||
notificationList.removeAll { it.type == type }
|
||||
}
|
||||
}
|
||||
|
||||
// update the database
|
||||
app.db.metadataDao().setAllNotified(true)
|
||||
if (notificationList.isNotEmpty())
|
||||
|
@ -41,8 +41,9 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
|
||||
Lesson::class,
|
||||
ConfigEntry::class,
|
||||
LibrusLesson::class,
|
||||
TimetableManual::class,
|
||||
Metadata::class
|
||||
], version = 77)
|
||||
], version = 79)
|
||||
@TypeConverters(
|
||||
ConverterTime::class,
|
||||
ConverterDate::class,
|
||||
@ -80,6 +81,7 @@ abstract class AppDb : RoomDatabase() {
|
||||
abstract fun timetableDao(): TimetableDao
|
||||
abstract fun configDao(): ConfigDao
|
||||
abstract fun librusLessonDao(): LibrusLessonDao
|
||||
abstract fun timetableManualDao(): TimetableManualDao
|
||||
abstract fun metadataDao(): MetadataDao
|
||||
|
||||
companion object {
|
||||
@ -160,7 +162,9 @@ abstract class AppDb : RoomDatabase() {
|
||||
Migration74(),
|
||||
Migration75(),
|
||||
Migration76(),
|
||||
Migration77()
|
||||
Migration77(),
|
||||
Migration78(),
|
||||
Migration79()
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class ConverterDateInt {
|
||||
@TypeConverter
|
||||
fun toDate(value: Int): Date = Date.fromValue(value)
|
||||
fun toDate(value: Int): Date? = if (value == 0) null else Date.fromValue(value)
|
||||
|
||||
@TypeConverter
|
||||
fun toInt(date: Date?): Int = date?.value ?: 0
|
||||
|
@ -61,7 +61,7 @@ public abstract class EventDao {
|
||||
|
||||
@RawQuery(observedEntities = {Event.class})
|
||||
abstract LiveData<List<EventFull>> getAll(SupportSQLiteQuery query);
|
||||
public LiveData<List<EventFull>> getAll(int profileId, String filter) {
|
||||
public LiveData<List<EventFull>> getAll(int profileId, String filter, String limit) {
|
||||
String query = "SELECT \n" +
|
||||
"*, \n" +
|
||||
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
|
||||
@ -75,24 +75,24 @@ public abstract class EventDao {
|
||||
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = "+profileId+"\n" +
|
||||
"WHERE events.profileId = "+profileId+" AND events.eventBlacklisted = 0 AND "+filter+"\n" +
|
||||
"GROUP BY eventId\n" +
|
||||
"ORDER BY eventDate, eventStartTime ASC";
|
||||
"ORDER BY eventDate, eventStartTime ASC "+limit;
|
||||
Log.d("DB", query);
|
||||
return getAll(new SimpleSQLiteQuery(query));
|
||||
}
|
||||
public LiveData<List<EventFull>> getAll(int profileId) {
|
||||
return getAll(profileId, "1");
|
||||
return getAll(profileId, "1", "");
|
||||
}
|
||||
public List<EventFull> getAllNow(int profileId) {
|
||||
return getAllNow(profileId, "1");
|
||||
}
|
||||
public LiveData<List<EventFull>> getAllWhere(int profileId, String filter) {
|
||||
return getAll(profileId, filter);
|
||||
return getAll(profileId, filter, "");
|
||||
}
|
||||
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String filter) {
|
||||
return getAll(profileId, "eventType = "+type+" AND "+filter);
|
||||
return getAll(profileId, "eventType = "+type+" AND "+filter, "");
|
||||
}
|
||||
public LiveData<List<EventFull>> getAllByDate(int profileId, @NonNull Date date) {
|
||||
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
|
||||
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"'", "");
|
||||
}
|
||||
public List<EventFull> getAllByDateNow(int profileId, @NonNull Date date) {
|
||||
return getAllNow(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
|
||||
@ -100,7 +100,10 @@ public abstract class EventDao {
|
||||
public LiveData<List<EventFull>> getAllByDateTime(int profileId, @NonNull Date date, Time time) {
|
||||
if (time == null)
|
||||
return getAllByDate(profileId, date);
|
||||
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"' AND eventStartTime = '"+time.getStringValue()+"'");
|
||||
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"' AND eventStartTime = '"+time.getStringValue()+"'", "");
|
||||
}
|
||||
public LiveData<List<EventFull>> getAllNearest(int profileId, @NonNull Date today, int limit) {
|
||||
return getAll(profileId, "eventDate >= '"+today.getStringY_m_d()+"'", "LIMIT "+limit);
|
||||
}
|
||||
|
||||
@RawQuery
|
||||
|
@ -63,8 +63,8 @@ public abstract class MetadataDao {
|
||||
@Transaction
|
||||
public void setSeen(int profileId, Object o, boolean seen) {
|
||||
if (o instanceof Grade) {
|
||||
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).id, seen, false, 0)) == -1) {
|
||||
updateSeen(profileId, TYPE_GRADE, ((Grade) o).id, seen);
|
||||
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false, 0)) == -1) {
|
||||
updateSeen(profileId, TYPE_GRADE, ((Grade) o).getId(), seen);
|
||||
}
|
||||
}
|
||||
if (o instanceof Attendance) {
|
||||
@ -102,8 +102,8 @@ public abstract class MetadataDao {
|
||||
@Transaction
|
||||
public void setNotified(int profileId, Object o, boolean notified) {
|
||||
if (o instanceof Grade) {
|
||||
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).id, false, notified, 0)) == -1) {
|
||||
updateNotified(profileId, TYPE_GRADE, ((Grade) o).id, notified);
|
||||
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified, 0)) == -1) {
|
||||
updateNotified(profileId, TYPE_GRADE, ((Grade) o).getId(), notified);
|
||||
}
|
||||
}
|
||||
if (o instanceof Attendance) {
|
||||
|
@ -41,7 +41,7 @@ interface ProfileDao {
|
||||
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
|
||||
|
||||
@get:Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
||||
val profilesForSyncNow: List<Profile>
|
||||
val profilesForFirebaseNow: List<Profile>
|
||||
|
||||
@get:Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
||||
val idsForSyncNow: List<Int>
|
||||
|
@ -5,10 +5,9 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.*
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
@ -53,6 +52,9 @@ interface TimetableDao {
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
|
||||
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
|
||||
|
||||
@RawQuery(observedEntities = [Lesson::class])
|
||||
fun getRaw(query: SupportSQLiteQuery): LiveData<List<LessonFull>>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND type != -1 AND type != 0
|
||||
@ -67,12 +69,11 @@ interface TimetableDao {
|
||||
""")
|
||||
fun getChangesForDateNow(profileId: Int, date: Date): List<LessonFull>
|
||||
|
||||
@Query("""
|
||||
fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
|
||||
WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}"))
|
||||
ORDER BY id, type
|
||||
""")
|
||||
fun getForDate(profileId: Int, date: Date): LiveData<List<LessonFull>>
|
||||
"""))
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.db.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.TimetableManual
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
@Dao
|
||||
interface TimetableManualDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun add(timetableManual: TimetableManual)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun addAll(timetableManualList: List<TimetableManual>)
|
||||
|
||||
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId")
|
||||
fun getAll(profileId: Int): LiveData<List<TimetableManual>>
|
||||
|
||||
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND date >= :dateFrom AND date <= :dateTo")
|
||||
fun getAllByDateRange(profileId: Int, dateFrom: Date, dateTo: Date): LiveData<List<TimetableManual>>
|
||||
|
||||
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND (date IS NULL OR date = 0 OR date >= :dateFrom)")
|
||||
fun getAllToDisplay(profileId: Int, dateFrom: Date): LiveData<List<TimetableManual>>
|
||||
|
||||
@Delete
|
||||
fun delete(timetableManual: TimetableManual)
|
||||
|
||||
@Query("DELETE FROM timetableManual WHERE profileId = :profileId")
|
||||
fun clear(profileId: Int)
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.db.entity;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
|
||||
@Entity(tableName = "grades",
|
||||
primaryKeys = {"profileId", "gradeId"},
|
||||
indices = {@Index(value = {"profileId"})})
|
||||
public class Grade {
|
||||
public int profileId;
|
||||
|
||||
@ColumnInfo(name = "gradeId")
|
||||
public long id;
|
||||
|
||||
@ColumnInfo(name = "gradeCategory")
|
||||
public String category;
|
||||
@ColumnInfo(name = "gradeColor")
|
||||
public int color;
|
||||
@ColumnInfo(name = "gradeDescription")
|
||||
public String description;
|
||||
@ColumnInfo(name = "gradeComment")
|
||||
public String comment;
|
||||
@ColumnInfo(name = "gradeName")
|
||||
public String name;
|
||||
@ColumnInfo(name = "gradeValue")
|
||||
public float value;
|
||||
@ColumnInfo(name = "gradeValueMax")
|
||||
public float valueMax;
|
||||
@ColumnInfo(name = "gradeWeight")
|
||||
public float weight;
|
||||
@ColumnInfo(name = "gradeSemester")
|
||||
public int semester;
|
||||
@ColumnInfo(name = "gradeClassAverage")
|
||||
public float classAverage = -1;
|
||||
public static final int TYPE_NORMAL = 0;
|
||||
public static final int TYPE_SEMESTER1_PROPOSED = 1;
|
||||
public static final int TYPE_SEMESTER1_FINAL = 2;
|
||||
public static final int TYPE_SEMESTER2_PROPOSED = 3;
|
||||
public static final int TYPE_SEMESTER2_FINAL = 4;
|
||||
public static final int TYPE_YEAR_PROPOSED = 5;
|
||||
public static final int TYPE_YEAR_FINAL = 6;
|
||||
public static final int TYPE_POINT_AVG = 10;
|
||||
public static final int TYPE_POINT_SUM = 20;
|
||||
public static final int TYPE_DESCRIPTIVE = 30;
|
||||
public static final int TYPE_DESCRIPTIVE_TEXT = 31;
|
||||
public static final int TYPE_TEXT = 40;
|
||||
@ColumnInfo(name = "gradeType")
|
||||
public int type = TYPE_NORMAL;
|
||||
@ColumnInfo(name = "gradePointGrade")
|
||||
public boolean pointGrade = false;
|
||||
|
||||
/**
|
||||
* Applies for historical grades. It's the new/replacement grade's ID.
|
||||
*/
|
||||
@ColumnInfo(name = "gradeParentId")
|
||||
public long parentId = -1;
|
||||
|
||||
/**
|
||||
* Applies for current grades. If the grade was worse and this is the improved one.
|
||||
*/
|
||||
@ColumnInfo(name = "gradeIsImprovement")
|
||||
public boolean isImprovement = false;
|
||||
|
||||
public long teacherId;
|
||||
public long subjectId;
|
||||
|
||||
@Ignore
|
||||
public Grade() {}
|
||||
|
||||
public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) {
|
||||
this.profileId = profileId;
|
||||
this.id = id;
|
||||
this.category = category;
|
||||
this.color = color;
|
||||
this.description = description;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.weight = weight;
|
||||
this.semester = semester;
|
||||
this.teacherId = teacherId;
|
||||
this.subjectId = subjectId;
|
||||
}
|
||||
|
||||
/*@Ignore
|
||||
public Grade(int profileId, long id, String description, String name, float value, float weight, int semester, long teacherId, long categoryId, long subjectId) {
|
||||
this.profileId = profileId;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.weight = weight;
|
||||
this.semester = semester;
|
||||
this.teacherId = teacherId;
|
||||
//this.categoryId = categoryId;
|
||||
this.subjectId = subjectId;
|
||||
}*/
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user