Compare commits

...

59 Commits

Author SHA1 Message Date
4aa31424d6 [4.0-beta.12] Update build.gradle, signing and changelog. 2020-03-10 23:50:24 +01:00
8a825227cb [Timetable] Disable setting metadata for normal type lessons. 2020-03-10 23:45:40 +01:00
cc1b581d7e [Grades] Show custom plus/minus value annotation in GradeDetailsDialog. 2020-03-10 23:44:02 +01:00
9936d90ae2 [Dropdown/Date] Clarify strings a bit (makes sense during weekends). 2020-03-10 22:04:13 +01:00
df1a241b2b [Timetable] Fix showing "no timetable" when all nearest lessons are cancelled. Fix a crash in timetable fragment. 2020-03-10 22:01:30 +01:00
ae89b33fb7 [Events/Manual] Implement syncing timetable when no lessons for the selected date. 2020-03-10 21:49:02 +01:00
e05b483f5c [Grades] Disable counting grade value when custom values not specified. 2020-03-10 21:45:38 +01:00
715f536b23 [MainActivity] Fix some critical errors. 2020-03-10 20:57:04 +01:00
930813fb8a [Agenda] Try to fix agenda fragment not attached crashes. 2020-03-10 20:25:14 +01:00
acd5e9b998 [Timetable] Implement lazy day loading. Introduce TimetableManager class. 2020-03-10 19:27:18 +01:00
06011bf4ae [Grades] Add grades config and mark as read menu items. 2020-03-10 18:38:28 +01:00
30e15b813c [HotFix/Timers] Change timers intervals from 1s to 500ms. 2020-03-09 22:03:44 +01:00
fcd7a7f349 [Grades] Make home card use GradeView. Update GradeDetailsDialog text color. Remove deprecated items. 2020-03-09 20:39:48 +01:00
42ef40439e [Grades] Implement showing unseen badges and marking as seen. Change default "hide improved" config value. 2020-03-09 20:18:11 +01:00
098beb14fe [Timetable/Generate] Add automatic timetable sync when no timetable for the selected week. 2020-03-09 14:57:14 +01:00
0b186a754a [API/Librus] Implement behaviour grades with types. Use optional "Phrase" in text grades. 2020-03-08 20:12:37 +01:00
d00963b53d [Grades] Implement getting correct grade colors. 2020-03-08 19:39:23 +01:00
e282af0e80 [Grades] Add option to hide improved grades. Make counting average without weight configurable. 2020-03-08 17:57:44 +01:00
630361849c [Notifications] Implement Quiet hours. Add missing timetable manual strings. 2020-03-08 17:22:14 +01:00
88a1de50ca [Changelog] Update the changelog a bit. 2020-03-07 20:14:05 +01:00
d8263d0b6a [Timetable/Manual] Add database migration to implement new model. 2020-03-07 20:09:22 +01:00
611ab0f100 [Events/Manual] Create custom views for dropdowns. Simplify dialog code. Fix wrong start time saving. 2020-03-07 20:03:47 +01:00
70c307b796 [UI/Grades] Change some fonts. 2020-03-07 11:54:47 +01:00
054a233ad6 [API/Librus] Handle some more maintenance cases. 2020-03-07 09:45:45 +01:00
55268f1c43 [4.0-beta.11] Update build.gradle, signing and changelog. 2020-03-06 23:23:33 +01:00
1bec6d281c [Grades] Implement Grades editor. 2020-03-06 21:24:01 +01:00
f17a02be54 [Grades] Implement new Grades module (UI & API changes). 2020-03-06 21:09:05 +01:00
4e8fdd2225 [API/Idziennik] Fix incorrect exam type. 2020-03-06 09:25:35 +01:00
59819b4a96 [Base] Update TemplateFragment. 2020-03-04 19:09:53 +01:00
673378d8d9 [UI/Home] Improve no data text font in home cards. 2020-03-04 19:02:50 +01:00
30044d6b21 [Timetable] Ignore last lessons if cancelled and jump to the next day. 2020-03-03 18:06:55 +01:00
ee43d40680 [API/Librus] Fix device not registered error in push config. 2020-03-03 10:35:48 +01:00
1354faf8c7 [Dialog/GenerateBlockTimetable] Make better dialog layout. 2020-02-29 00:43:38 +01:00
1bfb3781ab [UI/Lists] Add missing item dividers. Try to improve attendance & grades design. 2020-02-28 23:45:46 +01:00
d7d0c6f822 [UI/Events] Add button tooltips in dialogs. Add showing weekday in home card. Add 'go to timetable' button in details dialog. 2020-02-28 23:01:38 +01:00
2bea18dc3c [Home/Events] Add new card to home fragment. Disable debug card swapping. 2020-02-28 22:38:03 +01:00
f998f2d956 [Home/Timetable] Remove "?" lessons from timetable card. 2020-02-28 21:10:03 +01:00
faa77ee5fb [Widgets/Timetable] Show crossed out classroom in lesson change if no new classroom specified. 2020-02-28 21:10:03 +01:00
88ec463284 [Gradle] Update gradle. 2020-02-28 18:33:28 +01:00
b7df71d7d9 [API/Grades] Fix proposed/final grades added date in Mobidziennik, Idziennik. 2020-02-27 23:41:41 +01:00
6a28dbd2c4 [API/Idziennik] Add changing the selected student/register (web) to get grades in some cases. 2020-02-27 23:36:41 +01:00
010f7fa1fe [API/Idziennik] Add getting lucky number from website. Fix API lucky number date. 2020-02-27 23:01:47 +01:00
209f98594f [Widgets/Timetable] Show lessons date in unified timetable widget. 2020-02-27 22:32:02 +01:00
54121c99a3 [Login/Captcha] Update captcha to fit smaller screens. Fix Librus invalid login error with captcha. 2020-02-26 21:27:03 +01:00
f6f1370edf [Debug] Add new debug mode. Include hidden Chucker in release. 2020-02-26 20:37:55 +01:00
d5863485f9 [API/Edudziennik] Fix getting attendance for the second semester. 2020-02-25 19:28:53 +01:00
afc88d316b [4.0-beta.10] Update build.gradle, signing and changelog. 2020-02-24 18:27:21 +01:00
b141279811 [API/Librus] Update Client ID. Add handling of invalid Client ID error. 2020-02-24 18:06:53 +01:00
1997ea25d5 [Gradle] Update gradle and libraries. 2020-02-24 15:29:18 +01:00
f4b49eecd4 [UI] Update theme accent colors. 2020-02-24 15:29:18 +01:00
a4493ec964 [Notifications] Add filtering notifications to show during sync. 2020-02-24 15:29:18 +01:00
af8bda9e92 [Dialog/Day] Add showing lessons count and length. 2020-02-23 23:17:28 +01:00
06d252e4ca [Notifications] Fix chucker notifications throwing an error toast. 2020-02-23 17:40:02 +01:00
67be456bb0 [Firebase/Librus] Implement Librus push registration and receiving. Fix not passing lastSync to endpoint. 2020-02-21 22:49:24 +01:00
aa5e225148 [Firebase/Vulcan] Fix not converting received string to JsonObject. 2020-02-21 22:32:50 +01:00
367f46fac8 [API/Librus] Fix captcha showing as incorrect login error. Add handling CSRF error. 2020-02-21 21:32:06 +01:00
d2f14093ec [API] Fix sync error in case of an internal, handled error. 2020-02-21 20:41:57 +01:00
43ed621879 [Errors] Fix error reporting from snackbar. 2020-02-21 20:35:18 +01:00
15c8134d13 [Firebase/Vulcan] Implement push notifications sync. 2020-02-20 21:20:51 +01:00
195 changed files with 6350 additions and 3907 deletions

View File

@ -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()

View File

@ -1,19 +1,23 @@
<h3>Wersja 4.0-beta.9, 2020-02-19</h3>
<h3>Wersja 4.0-beta.12, 2020-03-10</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 &#x1F44F;</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>

View File

@ -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 };
0xb8, 0x59, 0x75, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -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)

View File

@ -40,6 +40,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
@ -94,8 +95,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 +104,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 +1080,15 @@ fun Throwable.toErrorCode() = when (this) {
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
else -> ERROR_API_EXCEPTION
}
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 }

View File

@ -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
@ -286,8 +284,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)
@ -567,7 +563,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
EdziennikTask.syncProfile(
App.profileId,
listOf(navTargetId to fragmentParam),
arguments
arguments = arguments
).enqueue(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -906,7 +902,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)

View File

@ -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 }

View File

@ -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 }
}
}

View File

@ -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
@ -40,14 +41,19 @@ class ConfigSync(private val config: Config) {
| | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __|
| |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \
\___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/
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 +96,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 }
}
}

View File

@ -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 }

View File

@ -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))
}
}
}
}

View File

@ -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
@ -24,4 +25,28 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
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 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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}}
}

View File

@ -6,8 +6,8 @@ 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 {
@ -21,4 +21,4 @@ class ProfileConfigMigration(config: ProfileConfig) {
dataVersion = 1
}
}}
}
}

View File

@ -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"

View File

@ -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)
}

View File

@ -122,6 +122,8 @@ 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_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202

View File

@ -109,6 +109,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()
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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 -> {

View File

@ -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) }
}

View File

@ -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(

View File

@ -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 }
/* _
/\ (_)
/ \ _ __ _

View File

@ -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()
}

View File

@ -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 -> {

View File

@ -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()

View File

@ -69,7 +69,7 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik,
val luckyNumberObject = LuckyNumber(
data.profileId,
Date.getToday(),
luckyNumberDate,
luckyNumber
)

View File

@ -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(

View File

@ -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()

View File

@ -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
))
}
}

View File

@ -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()
}
}
}

View File

@ -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)

View File

@ -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; }
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _

View File

@ -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)
}
}

View File

@ -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(

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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
}

View File

@ -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(

View File

@ -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 ->

View File

@ -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
}

View File

@ -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) }
}

View File

@ -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_)
}*/
}
}

View File

@ -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(

View File

@ -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)

View File

@ -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()
}

View File

@ -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 -> {

View File

@ -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(

View File

@ -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

View File

@ -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) {

View File

@ -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 -> {

View File

@ -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()
}

View File

@ -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

View File

@ -68,6 +68,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)

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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) }
}

View File

@ -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()

View File

@ -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.MTIzNDU2Nzg5MDwj/ezwig===.$param2".sha256()
}
}

View File

@ -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)

View File

@ -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)

View File

@ -44,6 +44,17 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
}
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())

View File

@ -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()
}
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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)
}

View File

@ -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;
}*/
}

View File

@ -0,0 +1,92 @@
/*
* 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
/*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;
}*/
@Entity(tableName = "grades",
primaryKeys = ["profileId", "gradeId"],
indices = [Index(value = ["profileId"])])
open class Grade(
val profileId: Int,
@ColumnInfo(name = "gradeId")
val id: Long,
@ColumnInfo(name = "gradeName")
var name: String,
@ColumnInfo(name = "gradeType")
var type: Int,
@ColumnInfo(name = "gradeValue")
var value: Float,
@ColumnInfo(name = "gradeWeight")
var weight: Float,
@ColumnInfo(name = "gradeColor")
var color: Int,
@ColumnInfo(name = "gradeCategory")
var category: String?,
@ColumnInfo(name = "gradeDescription")
var description: String?,
@ColumnInfo(name = "gradeComment")
var comment: String?,
@ColumnInfo(name = "gradeSemester")
val semester: Int,
val teacherId: Long,
val subjectId: Long
) {
companion object {
const val TYPE_NORMAL = 0
const val TYPE_SEMESTER1_PROPOSED = 1
const val TYPE_SEMESTER1_FINAL = 2
const val TYPE_SEMESTER2_PROPOSED = 3
const val TYPE_SEMESTER2_FINAL = 4
const val TYPE_YEAR_PROPOSED = 5
const val TYPE_YEAR_FINAL = 6
const val TYPE_POINT_AVG = 10
const val TYPE_POINT_SUM = 20
const val TYPE_DESCRIPTIVE = 30
const val TYPE_DESCRIPTIVE_TEXT = 31
const val TYPE_TEXT = 40
}
@ColumnInfo(name = "gradeValueMax")
var valueMax: Float? = null
@ColumnInfo(name = "gradeClassAverage")
var classAverage: Float? = null
/**
* Applies for historical grades. It's the new/replacement grade's ID.
*/
@ColumnInfo(name = "gradeParentId")
var parentId: Long? = null
/**
* Applies for current grades. If the grade was worse and this is the improved one.
*/
@ColumnInfo(name = "gradeIsImprovement")
var isImprovement = false
@Ignore
var showAsUnseen = false
val isImproved
get() = parentId ?: -1L != -1L
}

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -66,6 +67,9 @@ open class Lesson(val profileId: Int, var id: Long) {
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
@Ignore
var showAsUnseen = false
override fun toString(): String {
return "Lesson(profileId=$profileId, " +
"id=$id, " +

View File

@ -43,10 +43,12 @@ class LoginStore(
fun getLoginData(key: String, defaultValue: Long) = data.getLong(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: Float) = data.getFloat(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: Char) = data.getChar(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: JsonObject) = data.getJsonObject(key) ?: defaultValue
fun putLoginData(key: String, value: Boolean) { data[key] = value }
fun putLoginData(key: String, value: String?) { data[key] = value }
fun putLoginData(key: String, value: Number) { data[key] = value }
fun putLoginData(key: String, value: Char) { data[key] = value }
fun putLoginData(key: String, value: JsonObject) { data[key] = value }
fun removeLoginData(key: String) { data.remove(key) }
fun copyFrom(args: Bundle) {

View File

@ -53,15 +53,8 @@ open class Profile(
const val REGISTRATION_UNSPECIFIED = 0
const val REGISTRATION_DISABLED = 1
const val REGISTRATION_ENABLED = 2
const val COLOR_MODE_DEFAULT = 0
const val COLOR_MODE_WEIGHTED = 1
const val AGENDA_DEFAULT = 0
const val AGENDA_CALENDAR = 1
const val YEAR_1_AVG_2_AVG = 0
const val YEAR_1_SEM_2_AVG = 1
const val YEAR_1_AVG_2_SEM = 2
const val YEAR_1_SEM_2_SEM = 3
const val YEAR_ALL_GRADES = 4
}
override var image: String? = null

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
*/
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@Entity(tableName = "timetableManual",
indices = [
Index(value = ["profileId", "date"]),
Index(value = ["profileId", "weekDay"])
])
class TimetableManual(
val profileId: Int,
var type: Int,
var repeatBy: Int,
@PrimaryKey(autoGenerate = true)
var id: Int = 0
) {
companion object {
const val TYPE_NORMAL = 0
const val TYPE_CANCELLED = 1
const val TYPE_CHANGE = 2
const val TYPE_SHIFTED_SOURCE = 3
const val TYPE_SHIFTED_TARGET = 4
const val TYPE_REMOVED = 5
const val TYPE_CLASSROOM = 6
const val REPEAT_WEEKLY = 0
const val REPEAT_ONCE = 1
const val REPEAT_BY_SUBJECT = 2
}
// `date` for one time lesson
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
var date: Date? = null
// `weekDay` for repeating lesson (every week)
var weekDay: Int? = null
var lessonNumber: Int? = null
var startTime: Time? = null
var endTime: Time? = null
var subjectId: Long? = null
var teacherId: Long? = null
var teamId: Long? = null
var classroom: String? = null
fun verifyParams(): Boolean {
return when (repeatBy) {
REPEAT_WEEKLY -> date == null && weekDay != null
REPEAT_ONCE -> date != null && weekDay == null
REPEAT_BY_SUBJECT -> date == null && weekDay == null && subjectId != null
else -> false
}
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full;
import pl.szczodrzynski.edziennik.data.db.entity.Grade;
public class GradeFull extends Grade {
//public String category = "";
//public int color;
public String subjectLongName = "";
public String subjectShortName = "";
public String teacherFullName = "";
// metadata
public boolean seen;
public boolean notified;
public long addedDate;
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full
import pl.szczodrzynski.edziennik.data.db.entity.Grade
class GradeFull(
profileId: Int, id: Long, name: String, type: Int,
value: Float, weight: Float, color: Int,
category: String?, description: String?, comment: String?,
semester: Int, teacherId: Long, subjectId: Long
) : Grade(
profileId, id, name, type,
value, weight, color,
category, description, comment,
semester, teacherId, subjectId
) {
var subjectLongName: String? = null
var subjectShortName: String? = null
var teacherFullName: String? = null
// metadata
var seen = false
var notified = false
var addedDate: Long = 0
}

View File

@ -0,0 +1,44 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration78 : Migration(77, 78) {
override fun migrate(database: SupportSQLiteDatabase) {
// grades migration to kotlin
database.execSQL("ALTER TABLE grades RENAME TO _grades;")
database.execSQL("""CREATE TABLE grades (
profileId INTEGER NOT NULL,
gradeId INTEGER NOT NULL,
gradeName TEXT NOT NULL,
gradeType INTEGER NOT NULL,
gradeValue REAL NOT NULL,
gradeWeight REAL NOT NULL,
gradeColor INTEGER NOT NULL,
gradeCategory TEXT,
gradeDescription TEXT,
gradeComment TEXT,
gradeSemester INTEGER NOT NULL,
teacherId INTEGER NOT NULL,
subjectId INTEGER NOT NULL,
gradeValueMax REAL DEFAULT NULL,
gradeClassAverage REAL DEFAULT NULL,
gradeParentId INTEGER DEFAULT NULL,
gradeIsImprovement INTEGER NOT NULL,
PRIMARY KEY(profileId, gradeId)
);""")
database.execSQL("DROP INDEX IF EXISTS index_grades_profileId;")
database.execSQL("CREATE INDEX index_grades_profileId ON grades (profileId);")
database.execSQL("""INSERT INTO grades (profileId, gradeId, gradeName, gradeType, gradeValue, gradeWeight, gradeColor, gradeCategory, gradeDescription, gradeComment, gradeSemester, teacherId, subjectId, gradeValueMax, gradeClassAverage, gradeParentId, gradeIsImprovement)
SELECT profileId, gradeId, gradeName, gradeType, gradeValue, gradeWeight, gradeColor,
CASE gradeCategory WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeCategory END,
CASE gradeDescription WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeDescription END,
CASE gradeComment WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeComment END,
gradeSemester, teacherId, subjectId,
CASE gradeValueMax WHEN 0 THEN NULL ELSE gradeValueMax END,
CASE gradeClassAverage WHEN -1 THEN NULL ELSE gradeClassAverage END,
CASE gradeParentId WHEN -1 THEN NULL ELSE gradeParentId END,
gradeIsImprovement FROM _grades;""")
database.execSQL("DROP TABLE _grades;")
}
}

View File

@ -0,0 +1,27 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration79 : Migration(78, 79) {
override fun migrate(database: SupportSQLiteDatabase) {
// manual timetable implementation
database.execSQL("""CREATE TABLE timetableManual (
profileId INTEGER NOT NULL,
id INTEGER PRIMARY KEY NOT NULL,
type INTEGER NOT NULL,
repeatBy INTEGER NOT NULL DEFAULT 0,
date INTEGER DEFAULT NULL,
weekDay INTEGER DEFAULT NULL,
lessonNumber INTEGER DEFAULT NULL,
startTime TEXT DEFAULT NULL,
endTime TEXT DEFAULT NULL,
subjectId INTEGER DEFAULT NULL,
teacherId INTEGER DEFAULT NULL,
teamId INTEGER DEFAULT NULL,
classroom TEXT DEFAULT NULL
)""")
database.execSQL("CREATE INDEX index_timetableManual_profileId_date ON timetableManual (profileId, date)")
database.execSQL("CREATE INDEX index_timetableManual_profileId_weekDay ON timetableManual (profileId, weekDay)")
}
}

View File

@ -87,7 +87,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
if (remoteMessage.getData().get("id_wiadomosci") != null) {
d(TAG, "Syncing profile " + profile.getId());
EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
EdziennikTask.Companion.syncProfile(profile.getId(), null, null, null).enqueue(app);
} else {
/*app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
.withProfileData(profile.id, profile.name)
@ -98,7 +98,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
app.notifier.postAll(profile);
app.saveConfig("notifications");*/
d(TAG, "Syncing profile " + profile.getId());
EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
EdziennikTask.Companion.syncProfile(profile.getId(), null, null, null).enqueue(app);
}
}
});

View File

@ -145,25 +145,31 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
event.topic
)*/
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
val notification = Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = message,
type = type,
profileId = profile?.id,
profileName = profile?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
if (!notificationFilter.contains(type)) {
val notification = Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = message,
type = type,
profileId = profile?.id,
profileName = profile?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
notificationList += notification
}
events += event
metadataList += metadata
notificationList += notification
}
app.db.eventDao().addAll(events)
app.db.metadataDao().addAllReplace(metadataList)
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
if (notificationList.isNotEmpty()) {
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
}
private fun unsharedEvent(teamCode: String, eventId: Long, message: String) {
@ -172,19 +178,26 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId }
val notification = Notification(
id = Notification.buildId(profile?.id ?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
text = message,
type = Notification.TYPE_REMOVED_SHARED_EVENT,
profileId = profile?.id,
profileName = profile?.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA
)
notificationList += notification
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) {
val notification = Notification(
id = Notification.buildId(profile?.id
?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
text = message,
type = Notification.TYPE_REMOVED_SHARED_EVENT,
profileId = profile?.id,
profileName = profile?.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA
)
notificationList += notification
}
app.db.eventDao().remove(team.profileId, eventId)
}
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
if (notificationList.isNotEmpty()) {
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
}
}

View File

@ -5,7 +5,10 @@
package pl.szczodrzynski.edziennik.data.firebase
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.*
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.getString
@ -23,14 +26,44 @@ class SzkolnyLibrusFirebase(val app: App, val profiles: List<Profile>, val messa
"objectType": "Calendars/TeacherFreeDays",
}*/
init { run {
val apiLogin = message.data.getString("userId") ?: return@run
val type = message.data.getString("objectType") ?: return@run
val accountLogin = message.data.getString("userId")?.replace("u", "") ?: return@run
val tasks = profiles.filter {
it.getStudentData("accountLogin", "") == apiLogin
}.map {
EdziennikTask.syncProfile(it.id)
/* ./src/store/modules/helpers/change-processor.js */
val endpoints = when (type) {
"Notes" -> listOf(ENDPOINT_LIBRUS_API_NOTICES)
"Grades" -> listOf(ENDPOINT_LIBRUS_API_NORMAL_GRADES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS)
"PointGrades" -> listOf(ENDPOINT_LIBRUS_API_POINT_GRADES, ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES)
"DescriptiveGrades" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES)
"DescriptiveGrades/Text/Categories" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES)
"DescriptiveTextGrades" -> listOf(ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES)
"TextGrades" -> listOf(ENDPOINT_LIBRUS_API_TEXT_GRADES)
"BehaviourGrades/Points" -> listOf(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES, ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS)
"BehaviourGrades" -> listOf()
"Attendances" -> listOf(ENDPOINT_LIBRUS_API_ATTENDANCES)
"HomeWorks" -> listOf(ENDPOINT_LIBRUS_API_EVENTS)
"ParentTeacherConferences" -> listOf(ENDPOINT_LIBRUS_API_PT_MEETINGS)
"Calendars/ClassFreeDays" -> listOf(ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS)
"Calendars/TeacherFreeDays" -> listOf(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS)
"Calendars/SchoolFreeDays" -> listOf(ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS)
"Calendars/Substitutions" -> listOf(ENDPOINT_LIBRUS_API_TIMETABLES)
"HomeWorkAssignments" -> listOf(ENDPOINT_LIBRUS_API_HOMEWORK, ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK)
"SchoolNotices" -> listOf(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS)
"Messages" -> listOf(ENDPOINT_LIBRUS_MESSAGES_RECEIVED)
"LuckyNumbers" -> listOf(ENDPOINT_LIBRUS_API_LUCKY_NUMBER)
"Timetables" -> listOf(ENDPOINT_LIBRUS_API_TIMETABLES)
else -> return@run
}
if (endpoints.isEmpty())
return@run
val tasks = profiles.filter {
it.loginStoreType == LOGIN_TYPE_LIBRUS &&
it.getStudentData("accountLogin", "")?.replace("u", "") == accountLogin
}.map {
EdziennikTask.syncProfile(it.id, listOf(MainActivity.DRAWER_ITEM_HOME to 0), onlyEndpoints = endpoints)
}
IApiTask.enqueueAll(app, tasks)
}}
}

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED
@ -60,7 +61,8 @@ class SzkolnyMobidziennikFirebase(val app: App, val profiles: List<Profile>, val
}
val tasks = profiles.filter {
it.getStudentData("globalId", 0L) == globalId
it.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK &&
it.getStudentData("globalId", 0L) == globalId
}.map {
EdziennikTask.syncProfile(it.id, listOf(viewIdPair))
}

View File

@ -4,8 +4,13 @@
package pl.szczodrzynski.edziennik.data.firebase
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import java.util.*
class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
/*{
@ -20,7 +25,28 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
"title": "Frekwencja",
"message: "Uczeń Janósz otrzymał nieobecność na 7 lekcji"
}*/
init {
init { run {
val data = message.data.getString("data")?.toJsonObject() ?: return@run
val type = data.getString("table") ?: return@run
val studentId = data.getInt("pupilid")
}
/* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */
val viewIdPair = when (type.toLowerCase(Locale.ROOT)) {
"wiadomosc" -> MainActivity.DRAWER_ITEM_MESSAGES to Message.TYPE_RECEIVED
"ocena" -> MainActivity.DRAWER_ITEM_GRADES to 0
"uwaga" -> MainActivity.DRAWER_ITEM_BEHAVIOUR to 0
"frekwencja" -> MainActivity.DRAWER_ITEM_ATTENDANCE to 0
// this type is not even implemented in Dzienniczek+
"sprawdzian" -> MainActivity.DRAWER_ITEM_AGENDA to 0
else -> return@run
}
val tasks = profiles.filter {
it.loginStoreType == LOGIN_TYPE_VULCAN &&
it.getStudentData("studentId", 0) == studentId
}.map {
EdziennikTask.syncProfile(it.id, listOf(viewIdPair))
}
IApiTask.enqueueAll(app, tasks)
}}
}

View File

@ -90,7 +90,7 @@ class BellSyncDialog(
}
launch {
counterJob = startCoroutineTimer(repeatMillis = 1000) {
counterJob = startCoroutineTimer(repeatMillis = 500) {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.bell_sync_howto, bellTime.stringHM, bellDiffText)

View File

@ -14,6 +14,7 @@ import kotlinx.android.synthetic.main.row_lesson_change_item.view.*
import kotlinx.android.synthetic.main.row_teacher_absence_item.view.*
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.databinding.DialogDayBinding
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
@ -22,6 +23,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
@ -83,6 +85,27 @@ class DayDialog(
date.formattedString
)
val lessons = withContext(Dispatchers.Default) {
app.db.timetableDao().getForDateNow(profileId, date)
}.filter { it.type != Lesson.TYPE_NO_LESSONS }
if (lessons.isNotEmpty()) { run {
val startTime = lessons.first().startTime ?: return@run
val endTime = lessons.last().endTime ?: return@run
val diff = Time.diff(startTime, endTime)
b.lessonsInfo.setText(
R.string.dialog_day_lessons_info,
startTime.stringHM,
endTime.stringHM,
lessons.size.toString(),
diff.hour.toString(),
diff.minute.toString()
)
b.lessonsInfo.visibility = View.VISIBLE
}}
val lessonChanges = withContext(Dispatchers.Default) {
app.db.timetableDao().getChangesForDateNow(profileId, date)
}

View File

@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
@ -118,9 +119,43 @@ class EventDetailsDialog(
)
}
b.goToTimetableButton.setOnClickListener {
dialog.dismiss()
val dateStr = event.eventDate?.stringY_m_d ?: return@setOnClickListener
val intent =
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE)
Intent(TimetableFragment.ACTION_SCROLL_TO_DATE)
else if (activity is MainActivity)
Intent("android.intent.action.MAIN")
else
Intent(activity, MainActivity::class.java)
intent.apply {
putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
putExtra("timetableDate", dateStr)
}
if (activity is MainActivity)
activity.sendBroadcast(intent)
else
activity.startActivity(intent)
}
b.saveInCalendarButton.setOnClickListener {
openInCalendar()
}
b.goToTimetableButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show()
true
}
b.saveInCalendarButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_save_in_calendar, Toast.LENGTH_SHORT).show()
true
}
b.editButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true
}
}
private fun showRemoveEventDialog() {

View File

@ -8,14 +8,19 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.EventListItemBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class EventListAdapter(
val context: Context,
val simpleMode: Boolean = false,
val showDate: Boolean = false,
val showWeekDay: Boolean = false,
val onItemClick: ((event: EventFull) -> Unit)? = null,
val onEventEditClick: ((event: EventFull) -> Unit)? = null
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() {
@ -40,12 +45,16 @@ class EventListAdapter(
val bullet = ""
b.simpleMode = simpleMode
b.topic.text = event.topic
b.details.text = mutableListOf<CharSequence?>(
if (showWeekDay) Week.getFullDayName(event.eventDate.weekDay) else null,
if (showDate) event.eventDate.getRelativeString(context, 7) ?: event.eventDate.formattedStringShort else null,
event.typeName,
event.startTime?.stringHM ?: app.getString(R.string.event_all_day),
event.subjectLongName
if (simpleMode) null else event.startTime?.stringHM ?: app.getString(R.string.event_all_day),
if (simpleMode) null else event.subjectLongName
).concat(bullet)
b.addedBy.setText(
@ -65,11 +74,16 @@ class EventListAdapter(
b.typeColor.background?.setTintColor(event.getColor())
b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE
b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE
b.editButton.onClick {
onEventEditClick?.invoke(event)
}
b.editButton.setOnLongClickListener {
Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true
}
/*with(holder) {
b.eventListItemRoot.background.colorFilter = when (event.type) {
Event.TYPE_HOMEWORK -> PorterDuffColorFilter(0xffffffff.toInt(), PorterDuff.Mode.CLEAR)

View File

@ -12,24 +12,31 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class EventManualDialog(
@ -48,7 +55,7 @@ class EventManualDialog(
private const val TAG = "EventManualDialog"
}
private lateinit var job: Job
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -56,7 +63,6 @@ class EventManualDialog(
private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null
private val editingOwn = editingEvent?.sharedBy == "self"
@ -67,11 +73,14 @@ class EventManualDialog(
SzkolnyApi(app)
}
private var enqueuedWeekDialog: AlertDialog? = null
private var enqueuedWeekStart = Date.getToday()
init { run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
EventBus.getDefault().register(this)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_event_manual_title)
@ -85,6 +94,7 @@ class EventManualDialog(
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@EventManualDialog)
}
.setCancelable(false)
.create()
@ -104,12 +114,6 @@ class EventManualDialog(
show()
}
event = editingEvent?.clone() ?: Event().also { event ->
event.profileId = profileId
defaultType?.let {
event.type = it
}
}
b.shareSwitch.isChecked = editingShared
b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn)
@ -144,41 +148,138 @@ class EventManualDialog(
else -> R.string.dialog_event_manual_share_first_notice
}
b.shareDetails.setText(text, event.sharedByName ?: "")
b.shareDetails.setText(text, editingEvent?.sharedByName ?: "")
}
private fun syncTimetable(date: Date) {
if (enqueuedWeekDialog != null) {
return
}
if (app.profile.getStudentData("timetableNotPublic", false)) {
return
}
val weekStart = date.weekStart
enqueuedWeekDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.timetable_syncing_text)
.setCancelable(false)
.show()
enqueuedWeekStart = weekStart
EdziennikTask.syncProfile(
profileId = App.profileId,
viewIds = listOf(
MainActivity.DRAWER_ITEM_TIMETABLE to 0
),
arguments = JsonObject(
"weekStart" to weekStart.stringY_m_d
)
).enqueue(activity)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
if (event.profileId == App.profileId) {
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
launch {
b.timeDropdown.loadItems()
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
dialog.dismiss()
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
}
private fun loadLists() { launch {
with (b.dateDropdown) {
db = app.db
profileId = App.profileId
showWeekDays = false
showDays = true
showOtherDate = true
defaultLesson?.let {
nextLessonSubjectId = it.displaySubjectId
nextLessonSubjectName = it.displaySubjectName
nextLessonTeamId = it.displayTeamId
}
loadItems()
selectDefault(editingEvent?.eventDate)
selectDefault(defaultLesson?.displayDate ?: defaultDate)
onDateSelected = { date, lesson ->
b.timeDropdown.deselect()
b.timeDropdown.lessonsDate = date
this@EventManualDialog.launch {
if (!b.timeDropdown.loadItems())
syncTimetable(date)
lesson?.displayStartTime?.let { b.timeDropdown.selectTime(it) }
lesson?.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson?.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson?.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
}
with (b.timeDropdown) {
db = app.db
profileId = App.profileId
showAllDay = true
showCustomTime = true
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
displayMode = DISPLAY_LESSONS
if (!loadItems())
syncTimetable(lessonsDate ?: Date.getToday())
selectDefault(editingEvent?.startTime)
selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
onLessonSelected = { lesson ->
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
with (b.teamDropdown) {
db = app.db
profileId = App.profileId
showNoTeam = true
loadItems()
selectTeamClass()
selectDefault(editingEvent?.teamId)
selectDefault(defaultLesson?.displayTeamId)
}
with (b.subjectDropdown) {
db = app.db
profileId = App.profileId
showNoSubject = true
showCustomSubject = false
loadItems()
selectDefault(editingEvent?.subjectId)
selectDefault(defaultLesson?.displaySubjectId)
}
with (b.teacherDropdown) {
db = app.db
profileId = App.profileId
showNoTeacher = true
loadItems()
selectDefault(editingEvent?.teacherId)
selectDefault(defaultLesson?.displayTeacherId)
}
val deferred = async(Dispatchers.Default) {
// get the team list
val teams = app.db.teamDao().getAllNow(profileId)
b.teamDropdown.clear()
b.teamDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_team),
""
)
b.teamDropdown += teams.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
// get the subject list
val subjects = app.db.subjectDao().getAllNow(profileId)
b.subjectDropdown.clear()
b.subjectDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_subject),
""
)
b.subjectDropdown += subjects.map { TextInputDropDown.Item(it.id, it.longName, tag = it) }
// get the teacher list
val teachers = app.db.teacherDao().getAllNow(profileId)
b.teacherDropdown.clear()
b.teacherDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_teacher),
""
)
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
@ -186,13 +287,10 @@ class EventManualDialog(
}
deferred.await()
b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it.toLong())
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
@ -201,9 +299,6 @@ class EventManualDialog(
// copy IDs from event being edited
editingEvent?.let {
b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId)
b.topic.setText(it.topic)
b.typeDropdown.select(it.type.toLong())?.let { item ->
customColor = (item.tag as EventType).color
@ -215,8 +310,6 @@ class EventManualDialog(
// copy IDs from the LessonFull
defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
b.typeDropdown.setOnChangeListener {
@ -244,278 +337,8 @@ class EventManualDialog(
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates()
}}
private fun loadDates() { launch {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val deferred = async(Dispatchers.Default) {
val dates = mutableListOf<TextInputDropDown.Item>()
// item choosing the next lesson of specific subject
b.subjectDropdown.selected?.let {
if (it.tag is Subject) {
dates += TextInputDropDown.Item(
-it.id,
activity.getString(R.string.dialog_event_manual_date_next_lesson, it.tag.longName)
)
}
}
// TODAY
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
dates += TextInputDropDown.Item(
-1L,
activity.getString(R.string.dialog_event_manual_date_other)
)
dates
}
val dates = deferred.await()
b.dateDropdown.clear().append(dates)
defaultDate?.let {
event.eventDate = it
if (b.dateDropdown.select(it) == null)
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
editingEvent?.eventDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
defaultLesson?.displayDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
if (b.dateDropdown.selected == null) {
b.dateDropdown.select(today.toLong())
}
b.dateDropdown.isEnabled = true
b.dateDropdown.setOnChangeListener { item ->
when {
// next lesson with specified subject
item.id < -1 -> {
val teamId = defaultLesson?.teamId ?: -1
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.select(TextInputDropDown.Item(
lessonDate.value.toLong(),
lessonDate.formattedString,
tag = lessonDate
))
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
defaultLoaded = false
loadHours(it.displayStartTime)
})
return@setOnChangeListener false
}
// custom date
item.id == -1L -> {
MaterialDatePicker.Builder
.datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
b.dateDropdown.select(TextInputDropDown.Item(
dateSelected.value.toLong(),
dateSelected.formattedString,
tag = dateSelected
))
loadHours()
}
show(this@EventManualDialog.activity.supportFragmentManager, "MaterialDatePicker")
}
return@setOnChangeListener false
}
// a specific date
else -> {
b.dateDropdown.select(item)
loadHours()
}
}
return@setOnChangeListener true
}
loadHours()
}}
private fun loadHours(defaultHour: Time? = null) {
b.timeDropdown.isEnabled = false
// get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
// get all lessons for selected date
app.db.timetableDao().getForDate(profileId, date).observeOnce(activity, Observer { lessons ->
val hours = mutableListOf<TextInputDropDown.Item>()
// add All day time choice
hours += TextInputDropDown.Item(
0L,
activity.getString(R.string.dialog_event_manual_all_day)
)
lessons.forEach { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
hours += TextInputDropDown.Item(
-2L,
activity.getString(R.string.dialog_event_manual_no_lessons)
)
return@forEach
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
hours += TextInputDropDown.Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
b.timeDropdown.clear().append(hours)
if (defaultLoaded) {
b.timeDropdown.deselect()
// select the TEAM_CLASS if possible
b.teamDropdown.items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
}?.let {
b.teamDropdown.select(it)
} ?: b.teamDropdown.deselect()
// clear subject, teacher selection
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
}
else {
val setTime: (Time) -> Unit = {
event.startTime = it
if (b.timeDropdown.select(it) == null)
b.timeDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.stringHM,
tag = it
))
}
defaultTime?.let(setTime)
editingEvent?.startTime?.let(setTime)
defaultLesson?.displayStartTime?.let(setTime)
defaultHour?.let(setTime)
}
defaultLoaded = true
b.timeDropdown.isEnabled = true
// attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item ->
when (item.id) {
// no lessons this day
-2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// custom start hour
-1L -> return@setOnChangeListener false
// selected a specific lesson
else -> {
if (item.tag is LessonFull) {
// update team, subject, teacher dropdowns,
// using the LessonFull from item tag
b.teamDropdown.deselect()
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
item.tag.displayTeamId?.let {
b.teamDropdown.select(it)
}
item.tag.displaySubjectId?.let {
b.subjectDropdown.select(it)
}
item.tag.displayTeacherId?.let {
b.teacherDropdown.select(it)
}
}
}
}
return@setOnChangeListener true
}
})
}
private fun showRemoveEventDialog() {
val shareNotice = when {
editingShared && editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self)
@ -541,22 +364,29 @@ class EventManualDialog(
}
private fun saveEvent() {
val date = b.dateDropdown.selected?.tag.instanceOfOrNull<Date>()
val startTime = b.timeDropdown.selected?.tag.instanceOfOrNull<Time>()
val teamId = b.teamDropdown.selected?.id
val date = b.dateDropdown.getSelected() as? Date
val startTimePair = b.timeDropdown.getSelected() as? Pair<*, *>
val startTime = startTimePair?.first as? Time
val teamId = b.teamDropdown.getSelected() as? Long
val type = b.typeDropdown.selected?.id
val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.selected?.id
val teacherId = b.teacherDropdown.selected?.id
val subjectId = b.subjectDropdown.getSelected() as? Long
val teacherId = b.teacherDropdown.getSelected() as? Long
val share = b.shareSwitch.isChecked
b.dateDropdown.error = null
b.teamDropdown.error = null
b.typeDropdown.error = null
b.topic.error = null
var isError = false
if (date == null) {
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
isError = true
}
if (share && teamId == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
isError = true

View File

@ -1,129 +0,0 @@
package pl.szczodrzynski.edziennik.ui.dialogs.grade;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.AsyncTask;
import android.view.View;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.afollestad.materialdialogs.MaterialDialog;
import java.text.DecimalFormat;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.entity.Grade;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding;
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListAdapter;
import pl.szczodrzynski.edziennik.utils.Colors;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.COLOR_MODE_DEFAULT;
public class GradeDetailsDialog {
private App app;
private Context context;
private int profileId;
public GradeDetailsDialog(Context context) {
this.context = context;
this.profileId = App.Companion.getProfileId();
}
public GradeDetailsDialog(Context context, int profileId) {
this.context = context;
this.profileId = profileId;
}
public MaterialDialog dialog;
private DialogGradeDetailsBinding b;
private DialogInterface.OnDismissListener dismissListener;
public boolean callDismissListener = true;
public GradeDetailsDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
return this;
}
public void performDismiss(DialogInterface dialogInterface) {
if (callDismissListener && dismissListener != null) {
dismissListener.onDismiss(dialogInterface);
}
callDismissListener = true;
}
public void show(App _app, GradeFull grade)
{
this.app = _app;
dialog = new MaterialDialog.Builder(context)
.customView(R.layout.dialog_grade_details, true)
.positiveText(R.string.close)
.autoDismiss(false)
.onPositive((dialog, which) -> dialog.dismiss())
.dismissListener(this::performDismiss)
.show();
View root = dialog.getCustomView();
assert root != null;
b = DialogGradeDetailsBinding.bind(root);
b.setGrade(grade);
int gradeColor;
if (App.Companion.getConfig().getFor(profileId).getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
}
else {
gradeColor = Colors.gradeToColor(grade);
}
DecimalFormat format = new DecimalFormat("#.##");
if (grade.weight < 0) {
grade.weight *= -1;
}
if (grade.type == Grade.TYPE_DESCRIPTIVE || grade.type == Grade.TYPE_DESCRIPTIVE_TEXT || grade.type == Grade.TYPE_TEXT || grade.type == Grade.TYPE_POINT_SUM) {
b.setWeightText(null);
grade.weight = 0;
}
else {
if (grade.type == Grade.TYPE_POINT_AVG) {
b.setWeightText(app.getString(R.string.grades_max_points_format, format.format(grade.valueMax)));
}
else if (grade.weight == 0) {
b.setWeightText(app.getString(R.string.grades_weight_not_counted));
}
else {
b.setWeightText(app.getString(R.string.grades_weight_format, format.format(grade.weight)));
}
}
b.setCommentVisible(false);
b.setDevMode(App.Companion.getDevMode());
b.gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff);
b.gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY));
AsyncTask.execute(() -> {
List<GradeFull> historyList = app.db.gradeDao().getAllWithParentIdNow(profileId, grade.id);
if (historyList.size() == 0) {
b.setHistoryVisible(false);
return;
}
b.setHistoryVisible(true);
b.gradeHistoryNest.post(() -> {
b.gradeHistoryNest.setNestedScrollingEnabled(false);
b.gradeHistoryList.setHasFixedSize(false);
b.gradeHistoryList.setNestedScrollingEnabled(false);
b.gradeHistoryList.setLayoutManager(new LinearLayoutManager(context));
b.gradeHistoryList.setAdapter(new GradesListAdapter(context, historyList));
});
});
}
}

View File

@ -0,0 +1,92 @@
package pl.szczodrzynski.edziennik.ui.dialogs.grade
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class GradeDetailsDialog(
val activity: AppCompatActivity,
val grade: GradeFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "GradeDetailsDialog"
}
private lateinit var app: App
private lateinit var b: DialogGradeDetailsBinding
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogGradeDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
val manager = app.gradesManager
val gradeColor = manager.getGradeColor(grade)
b.grade = grade
b.weightText = manager.getWeightString(app, grade)
b.commentVisible = false
b.devMode = App.debugMode
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0x99000000.toInt() else 0x99ffffff.toInt())
b.gradeName.background.setTintColor(gradeColor)
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)
b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null
b.customValueLayout.isVisible = b.customValueDivider.isVisible
b.customValueButton.onClick {
GradesConfigDialog(activity, reloadOnDismiss = true)
}
launch {
val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getAllWithParentIdNow(App.profileId, grade.id)
}
if (historyList.isEmpty()) {
b.historyVisible = false
return@launch
}
b.historyVisible = true
//b.gradeHistoryNest.isNestedScrollingEnabled = false
b.gradeHistoryList.adapter = GradesAdapter(activity, {
GradeDetailsDialog(activity, it)
}).also { it.items = historyList.toMutableList() }
b.gradeHistoryList.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
}}
}

View File

@ -6,14 +6,20 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.config.ConfigGrades
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import it.sephiroth.android.library.numberpicker.doOnStopTrackingTouch
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.DialogConfigGradesBinding
import pl.szczodrzynski.edziennik.setOnSelectedListener
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
class GradesConfigDialog(
val activity: AppCompatActivity,
@ -42,6 +48,7 @@ class GradesConfigDialog(
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
@ -52,43 +59,84 @@ class GradesConfigDialog(
}}
private fun loadConfig() {
b.customPlusCheckBox.isChecked = profileConfig.plusValue != null
b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked
b.customMinusCheckBox.isChecked = profileConfig.minusValue != null
b.customMinusValue.isVisible = b.customMinusCheckBox.isChecked
b.customPlusValue.progress = profileConfig.plusValue ?: 0.5f
b.customMinusValue.progress = profileConfig.minusValue ?: 0.25f
when (config.orderBy) {
ConfigGrades.ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio
ConfigGrades.ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio
ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio
ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio
else -> null
}?.isChecked = true
when (profileConfig.colorMode) {
Profile.COLOR_MODE_DEFAULT -> b.gradeColorFromERegister
Profile.COLOR_MODE_WEIGHTED -> b.gradeColorByValue
COLOR_MODE_DEFAULT -> b.gradeColorFromERegister
COLOR_MODE_WEIGHTED -> b.gradeColorByValue
else -> null
}?.isChecked = true
when (profileConfig.yearAverageMode) {
Profile.YEAR_ALL_GRADES -> b.gradeAverageMode4
Profile.YEAR_1_AVG_2_AVG -> b.gradeAverageMode0
Profile.YEAR_1_SEM_2_AVG -> b.gradeAverageMode1
Profile.YEAR_1_AVG_2_SEM -> b.gradeAverageMode2
Profile.YEAR_1_SEM_2_SEM -> b.gradeAverageMode3
YEAR_ALL_GRADES -> b.gradeAverageMode4
YEAR_1_AVG_2_AVG -> b.gradeAverageMode0
YEAR_1_SEM_2_AVG -> b.gradeAverageMode1
YEAR_1_AVG_2_SEM -> b.gradeAverageMode2
YEAR_1_SEM_2_SEM -> b.gradeAverageMode3
else -> null
}?.isChecked = true
b.dontCountZeroToAverage.isChecked = !profileConfig.countZeroToAvg
b.hideImproved.isChecked = profileConfig.hideImproved
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
}
private fun saveConfig() {
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
}
private fun initView() {
b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ConfigGrades.ORDER_BY_DATE_DESC }
b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ConfigGrades.ORDER_BY_SUBJECT_ASC }
b.customPlusCheckBox.onChange { _, isChecked ->
b.customPlusValue.isVisible = isChecked
}
b.customMinusCheckBox.onChange { _, isChecked ->
b.customMinusValue.isVisible = isChecked
}
b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = Profile.COLOR_MODE_DEFAULT }
b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = Profile.COLOR_MODE_WEIGHTED }
// who the hell named those methods
// THIS SHIT DOES NOT EVEN WORK
b.customPlusValue.doOnStopTrackingTouch {
profileConfig.plusValue = it.progress
}
b.customMinusValue.doOnStopTrackingTouch {
profileConfig.minusValue = it.progress
}
b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_ALL_GRADES }
b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_AVG_2_AVG }
b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_SEM_2_AVG }
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_SEM_2_SEM }
b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC }
b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC }
b.dontCountZeroToAverage.setOnCheckedChangeListener { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_DEFAULT }
b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED }
b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_ALL_GRADES }
b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG }
b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG }
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
b.dontCountZeroToAverage.onChange { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }
b.averageWithoutWeightHelp.onClick {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.grades_config_average_without_weight)
.setMessage(R.string.grades_config_average_without_weight_message)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}

View File

@ -75,10 +75,6 @@ class ProfileRemoveDialog(
app.db.teamDao().clear(profileId)
app.db.timetableDao().clear(profileId)
val homeCards = app.config.ui.homeCards.toMutableList()
homeCards.removeAll { it.profileId == profileId }
app.config.ui.homeCards = homeCards
val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) {

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-21.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.onClick
import kotlin.coroutines.CoroutineContext
// TODO refactor dialog to allow configuring other profiles
// than the selected one in UI
class NotificationFilterDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "NotificationFilterDialog"
private val notificationTypes = listOf(
Notification.TYPE_TIMETABLE_LESSON_CHANGE to R.string.notification_type_timetable_lesson_change,
Notification.TYPE_NEW_GRADE to R.string.notification_type_new_grade,
Notification.TYPE_NEW_EVENT to R.string.notification_type_new_event,
Notification.TYPE_NEW_HOMEWORK to R.string.notification_type_new_homework,
Notification.TYPE_NEW_MESSAGE to R.string.notification_type_new_message,
Notification.TYPE_LUCKY_NUMBER to R.string.notification_type_lucky_number,
Notification.TYPE_NEW_NOTICE to R.string.notification_type_notice,
Notification.TYPE_NEW_ATTENDANCE to R.string.notification_type_attendance,
Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement,
Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event,
Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework,
Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event
)
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val notificationFilter = mutableListOf<Int>()
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
notificationFilter.clear()
notificationFilter += app.config.forProfile().sync.notificationFilter
val items = notificationTypes.map { app.getString(it.second) }.toTypedArray()
val checkedItems = notificationTypes.map { !notificationFilter.contains(it.first) }.toBooleanArray()
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_notification_filter_title)
//.setMessage(R.string.dialog_notification_filter_text)
.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
val type = notificationTypes[which].first
notificationFilter.remove(type)
if (!isChecked)
notificationFilter += type
}
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick {
if (notificationFilter.isEmpty()) {
app.config.forProfile().sync.notificationFilter = notificationFilter
dialog.dismiss()
return@onClick
}
// warn user when he tries to disable some notifications
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.are_you_sure)
.setMessage(R.string.notification_filter_warning)
.setPositiveButton(R.string.ok) { _, _ ->
app.config.forProfile().sync.notificationFilter = notificationFilter
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}}
}

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