Compare commits

..

65 Commits

Author SHA1 Message Date
219a7443c0 [4.0-rc.3] Update build.gradle, signing and changelog. 2020-03-29 15:31:49 +02:00
6deb408d80 [API/Librus] Fix attachment downloading, once again. 2020-03-29 15:30:30 +02:00
c6e1ff2164 [Events] Fix event sorting. Fix showing event teacher name. 2020-03-29 15:26:48 +02:00
bc0918a115 [API/Librus] Fix attachment downloading. 2020-03-29 15:16:35 +02:00
55ff9173be [API/Liburs] Fix unseen teacher absence metadata and add notifications for new teacher absences. 2020-03-28 17:08:36 +01:00
d4d548846f [Refactor] Refactor EventDao class. 2020-03-28 11:17:39 +01:00
ef4527f140 [Refactor] Rewrite events to Kotlin. 2020-03-27 18:51:56 +01:00
0b1e7242bb [API/Mobidziennik] Fix some errors. 2020-03-27 14:05:03 +01:00
30b6ac2a06 [4.0-rc.2] Update build.gradle, singing and changelog. 2020-03-26 20:46:03 +01:00
a7fa7cb5e4 [API/Librus] Fix a typo. 2020-03-26 20:45:46 +01:00
f3e87f9016 [API/Librus] Fix missing login data error. 2020-03-26 20:42:58 +01:00
a983af6c28 [4.0-rc.1] Update build.gradle, singing and changelog. 2020-03-26 20:40:00 +01:00
114c841f0c [API/Liburs] Fix Librus API push config endpoint. 2020-03-26 20:34:59 +01:00
e271048577 [UI] Clarify some errors. Fix deselecting the mini drawer. 2020-03-26 18:48:54 +01:00
b8c5925e82 [API/Librus] Fix attendance NPE. 2020-03-26 17:55:39 +01:00
9bda6c8869 [API/Librus] Disable push config with no premium. Disable API homework. Enable Synergia homework. 2020-03-26 16:31:11 +01:00
d1608d308c [API/Librus] Disable login with credentials in Messages. 2020-03-26 15:57:13 +01:00
b8e1e1d33a [Event/Manual] Fix dropdowns not showing any data. 2020-03-25 17:36:22 +01:00
8099a037e7 [Proguard] Update proguard rules to fix BetterLink and MiniDrawer. 2020-03-24 21:03:19 +01:00
af23c932a6 [API] Change the Cookie jar to fix most cookie problems. 2020-03-24 20:43:41 +01:00
4edabbb186 [Messages/Compose] Enable HTML messages for Idziennik. 2020-03-24 16:31:46 +01:00
37a5bea79b [Libraries] Update gradle, NavLib and Firebase. 2020-03-24 16:29:45 +01:00
40cdc7d713 [4.0-beta.14] Update build.gradle, signing and changelog. 2020-03-24 12:54:42 +01:00
49825aca48 [API/Librus] Fix message attachment downloading. 2020-03-24 12:51:49 +01:00
1d57c4e705 [Settings] Replace hardcoded Discord invite link with a redirect. 2020-03-21 16:57:01 +01:00
87ae5787ee [UX] Fix app quiting in home when back button opens drawer function active. 2020-03-20 21:39:38 +01:00
20f16c25a3 [API/Liburs] Fix getting wrong homework description in Synergia. 2020-03-20 15:47:12 +01:00
6f1ec79d9b [UX] Fix back button opens drawer function always opening the drawer. 2020-03-20 14:21:13 +01:00
18c7eea89c [API/Mobidziennik] Fix messages exception when no table found. Handle server problem maintenance error. 2020-03-19 23:25:01 +01:00
f73060aeb6 [API/Idziennik] Fix announcements error. 2020-03-19 23:04:23 +01:00
2f653b83b6 [Errors] Clarify some HTTP error explanations. 2020-03-19 23:00:39 +01:00
445dec907d [Home] Change card swipe direction to left. Add config dialog to bottom sheet. 2020-03-19 22:29:42 +01:00
927316d24b [Firebase] Disable sync by firebase when profile is excluded from auto sync. 2020-03-19 20:18:52 +01:00
3957453ed6 [UI] Fix displaying lists for correct profile in event manual dialog. 2020-03-19 20:09:35 +01:00
0296c704cb [UI] Update dialog NoDisplay theme. 2020-03-19 20:02:50 +01:00
1e7fe972de [API/Librus] Fix setting messages as read. 2020-03-19 19:49:55 +01:00
c95bc656ea [UI] Add context menus in messages and events to quickly run an action. 2020-03-19 17:55:12 +01:00
a082d95b04 [Events/Manual] Remove saving progress toasts. 2020-03-18 14:39:21 +01:00
6866dd4801 [Widgets/Timetable] Fix "no lessons" and "no timetable" texts overlapping. 2020-03-18 12:48:04 +01:00
2186da416e [Timetable] Fix displaying "no lessons" when all lessons in next 7 days are cancelled. 2020-03-18 12:45:53 +01:00
22d859fcde [UI] Set main snackbar dismiss timeout to 7 seconds. 2020-03-18 12:44:57 +01:00
39514b69b3 [API/Vulcan] Fix API url slash issue when migrating from 3.x. 2020-03-18 12:44:28 +01:00
c384736840 [Grades] Fix counting average without weight. 2020-03-17 16:06:22 +01:00
507657f273 [UI/Messages] Improve HTML lists presentation. 2020-03-17 16:05:21 +01:00
60641742ed [4.0-beta.13] Update build.gradle, signing and changelog. 2020-03-15 23:05:03 +01:00
0fc6f07986 [Homework] Fix homework list sorting. 2020-03-15 22:20:35 +01:00
1b2bdc0580 [Login] Add QR scanner to Vulcan & Librus JST login. Implement incorrect token error in Librus JST. 2020-03-15 21:46:46 +01:00
9bac239f77 [API/Librus] Add handling message not found error. Fix for duplicated errors and exceptions. 2020-03-15 20:15:10 +01:00
371acb2d2a [Events] Disable shared notification for past events. 2020-03-15 20:01:23 +01:00
454f82e139 [Events] Disable shared notification with registration disabled. Add registration enable prompt when sharing events. 2020-03-15 19:59:48 +01:00
e8da249353 [UI] Fix date dropdown selecting wrong month. Refactor event dialogs a bit. 2020-03-15 14:54:26 +01:00
c7950c53da [API/Vulcan] Fix adding unknown subject in timetable. Fix selecting correct TeamClass in timetable. 2020-03-15 12:12:00 +01:00
b5502478e4 [Dialog/EventManual] Add process dialog and fix some things. 2020-03-14 23:27:16 +01:00
4480a7e486 [API/Librus] Fix indicating parent account during first login. 2020-03-13 16:37:30 +01:00
7c7dff743b [API] Optimize App Sync a bit. 2020-03-13 16:22:43 +01:00
c568cd3f2e [Messages] Replace hardcoded message colors with brightened/darkened versions instead of white/black. 2020-03-12 18:46:16 +01:00
6ec2bc6f21 [API/Mobidziennik] Fix duplicated line breaks when getting message. 2020-03-12 13:55:02 +01:00
af3b6f3a97 [UI] Replace material date pickers with the DatePickerDialog. Add time picker to time dropdown. 2020-03-11 21:11:35 +01:00
d855118610 [Attendance] Revert changing attendance item font. 2020-03-11 19:58:56 +01:00
c9992d9fe8 [UI] Make fragments disable pull to refresh when not scrolled to the top. 2020-03-11 19:18:24 +01:00
85fe2636cc [Home] Disable pull to refresh while swiping a card. 2020-03-11 18:41:37 +01:00
35f4a31a76 [Home] Implement dismissing, adding and removing cards. Remove debug card. 2020-03-11 18:25:28 +01:00
1e494ebb70 [Feedback] Implement feedback fragment in feedback activity. 2020-03-11 17:36:41 +01:00
ed93627505 [Grades] Implement not counting selected grades to average. 2020-03-11 16:57:12 +01:00
b9b4b0036f [Grades] Update fonts and colors a bit. 2020-03-11 16:25:54 +01:00
166 changed files with 3271 additions and 1687 deletions

View File

@ -128,6 +128,7 @@ dependencies {
implementation "com.mikepenz:iconics-core:${versions.iconics}" implementation "com.mikepenz:iconics-core:${versions.iconics}"
implementation "com.mikepenz:iconics-views:${versions.iconics}" implementation "com.mikepenz:iconics-views:${versions.iconics}"
implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar" implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar"
implementation "com.mikepenz:materialize:1.2.1"
implementation "com.github.kuba2k2:NavLib:${versions.navlib}" implementation "com.github.kuba2k2:NavLib:${versions.navlib}"

View File

@ -31,6 +31,12 @@
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider -keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider -keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
-keep class .R -keep class .R
-keep class **.R$* { -keep class **.R$* {
<fields>; <fields>;

View File

@ -62,7 +62,7 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay"> android:theme="@style/AppTheme.Dark.NoDisplay">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
@ -84,7 +84,7 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" /> android:theme="@style/AppTheme.Dark.NoDisplay" />
<!-- NOTIFICATIONS --> <!-- NOTIFICATIONS -->
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider" <receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
android:label="@string/widget_notifications_title"> android:label="@string/widget_notifications_title">

View File

@ -1,12 +1,13 @@
<h3>Wersja 4.0-beta.12, 2020-03-10</h3> <h3>Wersja 4.0-rc.3, 2020-03-29</h3>
<ul> <ul>
<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><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><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych</li>
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</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>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>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>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> <li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
<li>Znaczki nieprzeczytanych informacji na obrazkach profili.</li>
<br> <br>
<br> <br>
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li> <li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
@ -21,18 +22,8 @@
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li> <li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</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> <li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
<li><strike>Występują natomiast nowe błędy, dlatego proszę o ich zgłaszanie :)</strike></li>
</ul> </ul>
<br> <br>
<br> <br>
<br>
<b>Uwaga.</b> Ponieważ to wersja <i>beta</i>, niektóre funkcje mogą nie działać prawidłowo.<br>
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>
</ul>
<br>
<br>
<br>
Dzięki za korzystanie ze Szkolnego!<br> Dzięki za korzystanie ze Szkolnego!<br>
<i>&copy; Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i> <i>&copy; Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0xb8, 0x59, 0x75, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0xa7, 0x84, 0xf9, 0xdc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -28,9 +28,6 @@ import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics import com.mikepenz.iconics.Iconics
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import im.wangchao.mhttp.MHttp import im.wangchao.mhttp.MHttp
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor
import kotlinx.coroutines.* import kotlinx.coroutines.*
import me.leolin.shortcutbadger.ShortcutBadger import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -41,6 +38,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.network.NetworkUtils import pl.szczodrzynski.edziennik.network.NetworkUtils
import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar
import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
@ -125,7 +123,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
.followSslRedirects(false) .followSslRedirects(false)
.build() .build()
} }
val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(this)) } val cookieJar by lazy { DumbCookieJar(this) }
/* _____ _ _ /* _____ _ _
/ ____(_) | | / ____(_) | |

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
*/
package pl.szczodrzynski.edziennik;
import android.graphics.Paint;
import android.widget.TextView;
import androidx.databinding.BindingAdapter;
public class Binding {
@BindingAdapter("strikeThrough")
public static void strikeThrough(TextView textView, Boolean strikeThrough) {
if (strikeThrough) {
textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
*/
package pl.szczodrzynski.edziennik
import android.graphics.Paint
import android.widget.TextView
import androidx.databinding.BindingAdapter
object Binding {
@JvmStatic
@BindingAdapter("strikeThrough")
fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
if (strikeThrough) {
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
}

View File

@ -2,6 +2,8 @@ package pl.szczodrzynski.edziennik
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -10,6 +12,7 @@ import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.graphics.Rect
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
@ -23,6 +26,7 @@ import android.util.Base64
import android.util.Base64.NO_WRAP import android.util.Base64.NO_WRAP
import android.util.Base64.encodeToString import android.util.Base64.encodeToString
import android.view.View import android.view.View
import android.view.WindowManager
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.RadioButton import android.widget.RadioButton
@ -51,6 +55,7 @@ import okhttp3.RequestBody
import okhttp3.TlsVersion import okhttp3.TlsVersion
import okio.Buffer import okio.Buffer
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Notification
@ -1003,6 +1008,7 @@ fun Context.getNotificationTitle(type: Int): String {
Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message
Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence
Notification.TYPE_GENERAL -> R.string.notification_type_general Notification.TYPE_GENERAL -> R.string.notification_type_general
else -> R.string.notification_type_general else -> R.string.notification_type_general
}) })
@ -1080,6 +1086,7 @@ fun Throwable.toErrorCode() = when (this) {
private fun ApiResponse.Error.toErrorCode() = when (this.code) { private fun ApiResponse.Error.toErrorCode() = when (this.code) {
else -> ERROR_API_EXCEPTION else -> ERROR_API_EXCEPTION
} }
fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this)
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? { inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
if (a != null && b != null) { if (a != null && b != null) {
@ -1092,3 +1099,71 @@ inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it } fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
@kotlin.jvm.JvmName("averageOrNullOfFloat") @kotlin.jvm.JvmName("averageOrNullOfFloat")
fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it } fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
fun String.copyToClipboard(context: Context) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText("Tekst", this)
clipboard.primaryClip = clipData
}
fun TextView.getTextPosition(range: IntRange): Rect {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// Initialize global value
var parentTextViewRect = Rect()
// Initialize values for the computing of clickedText position
//val completeText = parentTextView.text as SpannableString
val textViewLayout = this.layout
val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText)
val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText)
var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText)
var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText)
// Get the rectangle of the clicked text
val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText)
val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText)
val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect)
// Update the rectangle position to his real position on screen
val parentTextViewLocation = intArrayOf(0, 0)
this.getLocationOnScreen(parentTextViewLocation)
val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop)
parentTextViewRect.top += parentTextViewTopAndBottomOffset
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine) {
val screenHeight = windowManager.defaultDisplay.height
val dyTop = parentTextViewRect.top
val dyBottom = screenHeight - parentTextViewRect.bottom
val onTop = dyTop > dyBottom
if (onTop) {
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
} else {
parentTextViewRect = Rect()
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
this.compoundPaddingLeft -
this.scrollX
).toInt()
parentTextViewRect.right = (
parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
).toInt()
return parentTextViewRect
}

View File

@ -31,8 +31,8 @@ import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.ProfileDrawerItem import com.mikepenz.materialdrawer.model.ProfileDrawerItem
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.model.interfaces.IProfile import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -49,6 +49,7 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
@ -89,7 +90,6 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import pl.szczodrzynski.navlib.drawer.NavDrawer import pl.szczodrzynski.navlib.drawer.NavDrawer
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
import pl.szczodrzynski.navlib.drawer.items.withAppTitle
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@ -360,7 +360,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
false false
} }
drawerProfileLongClickListener = { _, profile, _, view -> drawerProfileLongClickListener = { _, profile, _, view ->
if (profile is ProfileDrawerItem) { if (view != null && profile is ProfileDrawerItem) {
showProfileContextMenu(profile, view) showProfileContextMenu(profile, view)
true true
} }
@ -444,6 +444,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// WHAT'S NEW DIALOG // WHAT'S NEW DIALOG
if (app.config.appVersion < BuildConfig.VERSION_CODE) { if (app.config.appVersion < BuildConfig.VERSION_CODE) {
// force an AppSync after update
app.config.sync.lastAppSync = 0L
ChangelogDialog(this) ChangelogDialog(this)
if (app.config.appVersion < 170) { if (app.config.appVersion < 170) {
//Intent intent = new Intent(this, ChangelogIntroActivity.class); //Intent intent = new Intent(this, ChangelogIntroActivity.class);
@ -721,6 +723,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
) )
true true
} }
"createManualEvent" -> {
val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday()
EventManualDialog(
this,
App.profileId,
defaultDate = date
)
true
}
else -> false else -> false
} }
if (handled && !navLoading) { if (handled && !navLoading) {
@ -903,7 +914,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
bottomSheet.removeAllContextual() bottomSheet.removeAllContextual()
bottomSheet.toggleGroupEnabled = false bottomSheet.toggleGroupEnabled = false
drawer.close() drawer.close()
drawer.setSelection(target.id, fireOnClick = false) if (drawer.getSelection() != target.id)
drawer.setSelection(target.id, fireOnClick = false)
navView.toolbar.setTitle(target.title ?: target.name) navView.toolbar.setTitle(target.title ?: target.name)
navView.bottomBar.fabEnable = false navView.bottomBar.fabEnable = false
navView.bottomBar.fabExtended = false navView.bottomBar.fabExtended = false
@ -1051,7 +1063,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val item = DrawerPrimaryItem() val item = DrawerPrimaryItem()
.withIdentifier(target.id.toLong()) .withIdentifier(target.id.toLong())
.withName(target.name) .withName(target.name)
.withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id)) .withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
.also { if (target.description != null) it.withDescription(target.description!!) } .also { if (target.description != null) it.withDescription(target.description!!) }
.also { if (target.icon != null) it.withIcon(target.icon!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) } .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
@ -1115,7 +1127,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
drawer.addProfileSettings(*drawerProfiles.toTypedArray()) drawer.addProfileSettings(*drawerProfiles.toTypedArray())
} }
private fun showProfileContextMenu(profile: IProfile<*>, view: View) { private fun showProfileContextMenu(profile: IProfile, view: View) {
val profileId = profile.identifier.toInt() val profileId = profile.identifier.toInt()
val popupMenu = PopupMenu(this, view) val popupMenu = PopupMenu(this, view)
popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings) popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings)
@ -1128,7 +1140,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
} }
loadTarget(DRAWER_ITEM_SETTINGS, null) loadTarget(DRAWER_ITEM_SETTINGS, null)
} else if (item.itemId == 2) { } else if (item.itemId == 2) {
ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?") ProfileRemoveDialog(this, profileId, profile.name?.getText(this) ?: "?")
} }
true true
} }
@ -1139,7 +1151,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
private var targetHomeId: Int = -1 private var targetHomeId: Int = -1
override fun onBackPressed() { override fun onBackPressed() {
if (!b.navView.onBackPressed()) { if (!b.navView.onBackPressed()) {
if (App.config.ui.openDrawerOnBackPressed) { if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome)
|| navTarget.id == DRAWER_ITEM_HOME)) {
b.navView.drawer.toggle() b.navView.drawer.toggle()
} else { } else {
navigateUp() navigateUp()

View File

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

View File

@ -20,6 +20,11 @@ class ConfigSync(private val config: Config) {
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true } get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
set(value) { config.set("syncEnabled", value); mSyncEnabled = value } set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
private var mWebPushEnabled: Boolean? = null
var webPushEnabled: Boolean
get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true }
set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value }
private var mSyncOnlyWifi: Boolean? = null private var mSyncOnlyWifi: Boolean? = null
var onlyWifi: Boolean var onlyWifi: Boolean
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates } get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
@ -35,6 +40,11 @@ class ConfigSync(private val config: Config) {
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true } get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value } set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
private var mLastAppSync: Long? = null
var lastAppSync: Long
get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
set(value) { config.set("lastAppSync", value); mLastAppSync = value }
/* ____ _ _ _ /* ____ _ _ _
/ __ \ (_) | | | | / __ \ (_) | | | |
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___ | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___

View File

@ -21,11 +21,6 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES } get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
set(value) { config.set("yearAverageMode", value); mYearAverageMode = value } set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
private var mCountZeroToAvg: Boolean? = null
var countZeroToAvg: Boolean
get() { mCountZeroToAvg = mCountZeroToAvg ?: config.values.get("countZeroToAvg", true); return mCountZeroToAvg ?: true }
set(value) { config.set("countZeroToAvg", value); mCountZeroToAvg = value }
private var mHideImproved: Boolean? = null private var mHideImproved: Boolean? = null
var hideImproved: Boolean var hideImproved: Boolean
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false } get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
@ -45,6 +40,11 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue } get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
set(value) { config.set("minusValue", value); mMinusValue = value } set(value) { config.set("minusValue", value); mMinusValue = value }
private var mDontCountEnabled: Boolean? = null
var dontCountEnabled: Boolean
get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false }
set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value }
private var mDontCountGrades: List<String>? = null private var mDontCountGrades: List<String>? = null
var dontCountGrades: List<String> var dontCountGrades: List<String>
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() } get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }

View File

@ -14,7 +14,7 @@ class ProfileConfigMigration(config: ProfileConfig) {
if (dataVersion < 1) { if (dataVersion < 1) {
grades.colorMode = COLOR_MODE_WEIGHTED grades.colorMode = COLOR_MODE_WEIGHTED
grades.countZeroToAvg = true grades.dontCountEnabled = false
grades.yearAverageMode = YEAR_ALL_GRADES grades.yearAverageMode = YEAR_ALL_GRADES
ui.agendaViewType = AGENDA_DEFAULT ui.agendaViewType = AGENDA_DEFAULT

View File

@ -22,6 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.toApiError
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -181,7 +182,7 @@ class ApiService : Service() {
is SzkolnyTask -> task.run(taskCallback) is SzkolnyTask -> task.run(taskCallback)
} }
} catch (e: Exception) { } catch (e: Exception) {
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e)) taskCallback.onError(e.toApiError(TAG))
} }
} }

View File

@ -124,6 +124,8 @@ const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183 const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184 const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184
const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185 const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185
const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186
const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
@ -140,6 +142,7 @@ const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216 const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216
const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217 const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217
const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301 const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302 const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302

View File

@ -70,13 +70,13 @@ val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
}, },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
} }
) )

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api package pl.szczodrzynski.edziennik.data.api
import kotlin.text.RegexOption.DOT_MATCHES_ALL import kotlin.text.RegexOption.DOT_MATCHES_ALL
import kotlin.text.RegexOption.IGNORE_CASE
object Regexes { object Regexes {
val STYLE_CSS_COLOR by lazy { val STYLE_CSS_COLOR by lazy {
@ -126,7 +127,7 @@ object Regexes {
val LIBRUS_ATTACHMENT_KEY by lazy { val LIBRUS_ATTACHMENT_KEY by lazy {
"""singleUseKey=([0-9A-f_]+)""".toRegex() """singleUseKey=([0-9A-z_]+)""".toRegex()
} }
@ -199,4 +200,20 @@ object Regexes {
val EDUDZIENNIK_TEACHERS by lazy { val EDUDZIENNIK_TEACHERS by lazy {
"""<div class="teacher">.*?<p>(.+?) (.+?)</p>""".toRegex(DOT_MATCHES_ALL) """<div class="teacher">.*?<p>(.+?) (.+?)</p>""".toRegex(DOT_MATCHES_ALL)
} }
val LINKIFY_DATE_YMD by lazy {
"""(1\d{3}|20\d{2})[\-./](1[0-2]|0?\d)[\-./]([1-2]\d|3[0-1]|0?\d)""".toRegex()
}
val LINKIFY_DATE_DMY by lazy {
"""(?<![\d\-./])([1-2]\d|3[0-1]|0?\d)[\-./](1[0-2]|0?\d)(?:[\-./](1\d{3}|2?0?\d{2}))?(?![\d\-/])""".toRegex()
}
val LINKIFY_DATE_ABSOLUTE by lazy {
"""([1-3][0-9]|[1-9])\s(sty|lut|mar|kwi|maj|cze|lip|sie|wrz|paź|lis|gru).*?\s(1[0-9]{3}|20[0-9]{2})?""".toRegex(IGNORE_CASE)
}
val LINKIFY_DATE_RELATIVE by lazy {
"""za\s([0-9]+)?\s?(dni|dzień|tydzień|tygodnie)""".toRegex(IGNORE_CASE)
}
} }

View File

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -43,8 +42,8 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
if (semester == null && url.contains("start")) { if (semester == null && url.contains("start")) {
profile?.also { profile -> profile?.also { profile ->
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com") val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
val semesterCookie = cookies.firstOrNull { it.name() == "semester" }?.value()?.toIntOrNull() val semesterCookie = cookies["semester"]?.toIntOrNull()
semesterCookie?.let { data.currentSemester = it } semesterCookie?.let { data.currentSemester = it }
@ -75,13 +74,7 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
} }
} }
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("dziennikel.appspot.com", "sessionid", data.webSessionId)
Cookie.Builder()
.name("sessionid")
.value(data.webSessionId!!)
.domain("dziennikel.appspot.com")
.secure().httpOnly().build()
))
Request.builder() Request.builder()
.url(url) .url(url)

View File

@ -39,17 +39,16 @@ class EdudziennikWebEvents(override val data: DataEdudziennik,
?: return@forEach ?: return@forEach
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
null, time = null,
title, topic = title,
-1, color = null,
Event.TYPE_CLASS_EVENT, type = Event.TYPE_CLASS_EVENT,
false, teacherId = -1,
-1, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -56,17 +56,16 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
val eventType = data.getEventType(eventTypeId, eventTypeName) val eventType = data.getEventType(eventTypeId, eventTypeName)
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
eventType.id, type = eventType.id,
false, teacherId = -1,
-1, subjectId = subject.id,
subject.id, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -52,17 +52,16 @@ class EdudziennikWebHomework(override val data: DataEdudziennik,
val topic = homeworkElement.child(4).text() val topic = homeworkElement.child(4).text()
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacher.id,
teacher.id, subjectId = subject.id,
subject.id, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -24,7 +24,7 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
onSuccess() onSuccess()
} }
else { else {
data.app.cookieJar.clearForDomain("dziennikel.appspot.com") data.app.cookieJar.clear("dziennikel.appspot.com")
if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) { if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
loginWithCredentials() loginWithCredentials()
} }
@ -59,8 +59,8 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
} }
} }
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com") val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
val sessionId = cookies.firstOrNull { it.name() == "sessionid" }?.value() val sessionId = cookies["sessionid"]
if (sessionId == null) { if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID) data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)

View File

@ -5,7 +5,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
import androidx.core.util.set import androidx.core.util.set
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
@ -24,18 +23,8 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
loginMethods.clear() loginMethods.clear()
if (isWebLoginValid()) { if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
app.cookieJar.saveFromResponse(null, listOf( app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId)
Cookie.Builder() app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth)
.name("ASP.NET_SessionId_iDziennik")
.value(webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
} }
if (isApiLoginValid()) if (isApiLoginValid())
loginMethods += LOGIN_METHOD_IDZIENNIK_API loginMethods += LOGIN_METHOD_IDZIENNIK_API

View File

@ -16,6 +16,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Announcement
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonObject import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebAnnouncements(override val data: DataIdziennik, class IdziennikWebAnnouncements(override val data: DataIdziennik,
@ -43,11 +45,11 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik,
for (jAnnouncementEl in json.getAsJsonArray("ListK")) { for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
val jAnnouncement = jAnnouncementEl.asJsonObject val jAnnouncement = jAnnouncementEl.asJsonObject
// jAnnouncement // jAnnouncement
val announcementId = jAnnouncement.get("Id").asLong val announcementId = jAnnouncement.getLong("Id") ?: -1
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.get("Autor").asString) val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "")
val addedDate = java.lang.Long.parseLong(jAnnouncement.get("DataDodania").asString.replace("[^\\d]".toRegex(), "")) val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis()
val startDate = Date.fromMillis(java.lang.Long.parseLong(jAnnouncement.get("DataWydarzenia").asString.replace("[^\\d]".toRegex(), ""))) val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) }
val announcementObject = Announcement( val announcementObject = Announcement(
profileId, profileId,

View File

@ -80,17 +80,16 @@ class IdziennikWebExams(override val data: DataIdziennik,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
examDate, date = examDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
eventType, type = eventType,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -67,17 +67,16 @@ class IdziennikWebHomework(override val data: DataIdziennik,
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
@ -24,22 +23,12 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
init { run { init { run {
if (data.isWebLoginValid()) { if (data.isWebLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId)
Cookie.Builder() data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth)
.name("ASP.NET_SessionId_iDziennik")
.value(data.webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(data.webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
onSuccess() onSuccess()
} }
else { else {
data.app.cookieJar.clearForDomain("iuczniowie.progman.pl") data.app.cookieJar.clear("iuczniowie.progman.pl")
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) { if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
loginWithCredentials() loginWithCredentials()
} }
@ -62,11 +51,11 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
// login succeeded: there is a start page // login succeeded: there is a start page
if (text.contains("czyWyswietlicDostepMobilny")) { if (text.contains("czyWyswietlicDostepMobilny")) {
val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl") val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl")
run { run {
data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER data.apiBearer = cookies["Bearer"]?: 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.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. */ data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus package pl.szczodrzynski.edziennik.data.api.edziennik.librus
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.currentTimeUnix import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API
@ -31,23 +30,11 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
loginMethods += LOGIN_METHOD_LIBRUS_API loginMethods += LOGIN_METHOD_LIBRUS_API
if (isSynergiaLoginValid()) { if (isSynergiaLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
app.cookieJar.saveFromResponse(null, listOf( app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
} }
if (isMessagesLoginValid()) { if (isMessagesLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
app.cookieJar.saveFromResponse(null, listOf( app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
} }
} }

View File

@ -64,7 +64,7 @@ val LibrusFeatures = listOf(
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf( Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
!data.app.config.sync.tokenLibrusList.contains(data.profileId) (data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId)
}, },
@ -116,11 +116,11 @@ val LibrusFeatures = listOf(
* Homework - using API. * Homework - using API.
* Sync only if account has premium access. * Sync only if account has premium access.
*/ */
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
(data as DataLibrus).isPremium (data as DataLibrus).isPremium
}, },*/
/** /**
* Behaviour - using API. * Behaviour - using API.
*/ */
@ -227,9 +227,9 @@ val LibrusFeatures = listOf(
*/ */
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)).withShouldSync { data -> ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data ->
!(data as DataLibrus).isPremium !(data as DataLibrus).isPremium
}, }*/,
/** /**
* Messages inbox - using messages website. * Messages inbox - using messages website.

View File

@ -12,7 +12,6 @@ import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.FileCallbackHandler import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import org.json.JSONObject import org.json.JSONObject
import org.json.XML import org.json.XML
import org.jsoup.Jsoup import org.jsoup.Jsoup
@ -55,13 +54,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
when { when {
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
else -> null
}?.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(text)
.withResponse(response))
return
} }
try { try {
@ -82,14 +88,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
} }
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument() val doc = docBuilder.newDocument()
@ -139,13 +138,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
when { when {
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
else -> null
}?.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(text)
.withResponse(response))
return
} }
try { try {
@ -166,14 +172,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
} }
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument() val doc = docBuilder.newDocument()
@ -253,10 +252,11 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
.enqueue() .enqueue()
} }
fun sandboxGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit, fun sandboxGetFile(tag: String, url: String, targetFile: File, onSuccess: (file: File) -> Unit,
method: Int = GET,
onProgress: (written: Long, total: Long) -> Unit) { onProgress: (written: Long, total: Long) -> Unit) {
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action") d(tag, "Request: Librus/Messages - $url")
val callback = object : FileCallbackHandler(targetFile) { val callback = object : FileCallbackHandler(targetFile) {
override fun onSuccess(file: File?, response: Response?) { override fun onSuccess(file: File?, response: Response?) {
@ -292,9 +292,14 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
Request.builder() Request.builder()
.url("$LIBRUS_SANDBOX_URL$action") .url(url)
.userAgent(SYNERGIA_USER_AGENT) .userAgent(SYNERGIA_USER_AGENT)
.post() .also {
when (method) {
POST -> it.post()
else -> it.get()
}
}
.callback(callback) .callback(callback)
.build() .build()
.enqueue() .enqueue()

View File

@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiAttendances(override val data: DataLibrus, class LibrusApiAttendances(override val data: DataLibrus,
override val lastSync: Long?, override val lastSync: Long?,
@ -42,10 +43,10 @@ class LibrusApiAttendances(override val data: DataLibrus,
val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id")
val semester = attendance.getInt("Semester") ?: return@forEach val semester = attendance.getInt("Semester") ?: return@forEach
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
val typeObject = data.attendanceTypes.get(type) val typeObject = data.attendanceTypes[type] ?: null
val topic = typeObject?.name ?: "" val topic = typeObject?.name ?: ""
val startTime = data.lessonRanges.get(lessonNo).startTime val startTime = data.lessonRanges.get(lessonNo)?.startTime
val lesson = if (lessonId != -1L) val lesson = if (lessonId != -1L)
data.librusLessons.singleOrNull { it.lessonId == lessonId } data.librusLessons.singleOrNull { it.lessonId == lessonId }
@ -59,14 +60,14 @@ class LibrusApiAttendances(override val data: DataLibrus,
semester, semester,
topic, topic,
lessonDate, lessonDate,
startTime, startTime ?: Time(0, 0, 0),
typeObject.type typeObject?.type ?: Attendance.TYPE_CUSTOM
) )
val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach)
data.attendanceList.add(attendanceObject) data.attendanceList.add(attendanceObject)
if(typeObject.type != Attendance.TYPE_PRESENT) { if(typeObject?.type != Attendance.TYPE_PRESENT) {
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,
Metadata.TYPE_ATTENDANCE, Metadata.TYPE_ATTENDANCE,

View File

@ -47,17 +47,16 @@ class LibrusApiEvents(override val data: DataLibrus,
val addedDate = Date.fromIso(event.getString("AddDate")) val addedDate = Date.fromIso(event.getString("AddDate"))
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId
teamId
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -34,17 +34,16 @@ class LibrusApiHomework(override val data: DataLibrus,
val addedDate = Date.fromY_m_d(homework.getString("Date")) val addedDate = Date.fromY_m_d(homework.getString("Date"))
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
null, time = null,
topic, topic = topic,
-1, color = null,
-1, type = -1,
false, teacherId = teacherId,
teacherId, subjectId = -1,
-1, teamId = -1
-1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -4,15 +4,12 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
import pl.szczodrzynski.edziennik.DAY import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -41,9 +38,10 @@ class LibrusApiLuckyNumber(override val data: DataLibrus,
luckyNumber luckyNumber
) )
//if (luckyNumberDate > Date.getToday()) { if (luckyNumberDate >= Date.getToday())
nextSync = luckyNumberDate.combineWith(Time(15, 0, 0)) nextSync = luckyNumberDate.combineWith(Time(15, 0, 0))
//} else
nextSync = System.currentTimeMillis() + 6*HOUR*1000
data.luckyNumberList.add(luckyNumberObject) data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add( data.metadataList.add(

View File

@ -39,17 +39,16 @@ class LibrusApiPtMeetings(override val data: DataLibrus,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_PT_MEETING, type = Event.TYPE_PT_MEETING,
false, teacherId = teacherId,
teacherId, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -21,6 +21,14 @@ class LibrusApiPushConfig(override val data: DataLibrus,
} }
init { data.app.config.sync.tokenLibrus?.also { tokenLibrus -> init { data.app.config.sync.tokenLibrus?.also { tokenLibrus ->
if(tokenLibrus.isEmpty()) {
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)
return@also
}
apiGet(TAG, "ChangeRegister", payload = JsonObject( apiGet(TAG, "ChangeRegister", payload = JsonObject(
"provider" to "FCM", "provider" to "FCM",
"device" to tokenLibrus, "device" to tokenLibrus,

View File

@ -59,7 +59,7 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus,
profileId, profileId,
Metadata.TYPE_TEACHER_ABSENCE, Metadata.TYPE_TEACHER_ABSENCE,
id, id,
profile?.empty ?: false, true,
profile?.empty ?: false, profile?.empty ?: false,
System.currentTimeMillis() System.currentTimeMillis()
)) ))

View File

@ -6,9 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.ERROR_FILE_DOWNLOAD import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.EXCEPTION_LIBRUS_MESSAGES_REQUEST
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
@ -53,11 +51,10 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus,
val attachmentKey = keyMatcher[1] val attachmentKey = keyMatcher[1]
getAttachmentCheckKey(attachmentKey) { getAttachmentCheckKey(attachmentKey) {
downloadAttachment(attachmentKey) downloadAttachment("${LIBRUS_SANDBOX_URL}CSDownload&singleUseKey=$attachmentKey", method = POST)
} }
} else { } else {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) downloadAttachment(downloadLink, method = GET)
.withApiResponse(doc.toString()))
} }
} }
} }
@ -91,10 +88,10 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus,
} }
} }
private fun downloadAttachment(attachmentKey: String) { private fun downloadAttachment(url: String, method: Int = GET) {
val targetFile = File(Utils.getStorageDir(), attachmentName) val targetFile = File(Utils.getStorageDir(), attachmentName)
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey", targetFile, { file -> sandboxGetFile(TAG, url, targetFile, { file ->
val event = AttachmentGetEvent( val event = AttachmentGetEvent(
profileId, profileId,

View File

@ -109,9 +109,14 @@ class LibrusMessagesGetList(override val data: DataLibrus,
id id
) )
element.select("isAnyFileAttached")?.text()?.let {
if (it == "1")
messageObject.overrideHasAttachments = true
}
data.messageIgnoreList.add(messageObject) data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
data.metadataList.add(Metadata( data.setSeenMetadataList.add(Metadata(
profileId, profileId,
Metadata.TYPE_MESSAGE, Metadata.TYPE_MESSAGE,
id, id,

View File

@ -29,7 +29,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
init { data.profile?.also { profile -> init { data.profile?.also { profile ->
synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf( synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf(
"dataOd" to "dataOd" to
if (!data.profile.empty) if (profile.empty)
profile.getSemesterStart(1).stringY_m_d profile.getSemesterStart(1).stringY_m_d
else else
Date.getToday().stringY_m_d, Date.getToday().stringY_m_d,
@ -54,7 +54,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id
?: -1 ?: -1
val topic = elements[2].text().trim() val topic = elements[2].text().trim()
val addedDate = Date.fromY_m_d(elements[4].text().trim()).inMillis val addedDate = Date.fromY_m_d(elements[4].text().trim())
val eventDate = Date.fromY_m_d(elements[6].text().trim()) val eventDate = Date.fromY_m_d(elements[6].text().trim())
val id = "/podglad/([0-9]+)'".toRegex().find( val id = "/podglad/([0-9]+)'".toRegex().find(
elements[9].select("input").attr("onclick") elements[9].select("input").attr("onclick")
@ -63,10 +63,25 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate) val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
val moreInfo = graphElements[2 * i + 1].select("td[title]") /*val moreInfo = graphElements[2 * i + 1].select("td[title]")
.attr("title").trim() .attr("title").trim()*/
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim() var description = ""
graphElements.forEach { graphEl ->
graphEl.select("td[title]")?.also {
val title = it.attr("title")
val r = "Temat: (.*?)<br.?/>Data udostępnienia: (.*?)<br.?/>Termin wykonania: (.*?)<br.?/>Treść: (.*)"
.toRegex(RegexOption.DOT_MATCHES_ALL).find(title) ?: return@forEach
val gTopic = r[1].trim()
val gAddedDate = Date.fromY_m_d(r[2].trim())
val gEventDate = Date.fromY_m_d(r[3].trim())
if (gTopic == topic && gAddedDate == addedDate && gEventDate == eventDate) {
description = r[4].replace("<br.?/>".toRegex(), "\n").trim()
return@forEach
}
}
}
val seen = when (profile.empty) { val seen = when (profile.empty) {
true -> true true -> true
@ -74,17 +89,16 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
"$topic\n$description", topic = "$topic\n$description",
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)
@ -94,7 +108,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
id, id,
seen, seen,
seen, seen,
addedDate addedDate.inMillis
)) ))
} }
} }

View File

@ -54,6 +54,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
return@portalGet return@portalGet
} }
val isParent = account.getString("group") == "parent"
val id = account.getInt("id") ?: continue val id = account.getInt("id") ?: continue
val login = account.getString("login") ?: continue val login = account.getString("login") ?: continue
val token = account.getString("accessToken") ?: continue val token = account.getString("accessToken") ?: continue
@ -69,7 +71,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
data.portalEmail, data.portalEmail,
studentNameLong, studentNameLong,
studentNameShort, studentNameShort,
null if (isParent) studentNameLong else null /* temporarily - there is no parent name provided, only the type */
).apply { ).apply {
studentData["accountId"] = id studentData["accountId"] = id
studentData["accountLogin"] = login studentData["accountLogin"] = login

View File

@ -137,6 +137,7 @@ class LibrusLoginApi {
"librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR "librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR
"librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED "librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED
"invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN "invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
"invalid_request" -> ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST
else -> ERROR_LOGIN_LIBRUS_API_OTHER else -> ERROR_LOGIN_LIBRUS_API_OTHER
}.let { errorCode -> }.let { errorCode ->
data.error(ApiError(TAG, errorCode) data.error(ApiError(TAG, errorCode)

View File

@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -64,21 +63,15 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
if (data.isMessagesLoginValid()) { if (data.isMessagesLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
onSuccess() onSuccess()
} }
else { else {
data.app.cookieJar.clearForDomain("wiadomosci.librus.pl") data.app.cookieJar.clear("wiadomosci.librus.pl")
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
loginWithSynergia() loginWithSynergia()
} }
else if (data.apiLogin != null && data.apiPassword != null) { else if (data.apiLogin != null && data.apiPassword != null && false) {
loginWithCredentials() loginWithCredentials()
} }
else { else {
@ -148,7 +141,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
private fun saveSessionId(response: Response?, text: String?) { private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID") var sessionId = data.app.cookieJar.get("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
sessionId = sessionId?.replace("MAINT", "") // dunno what's this sessionId = sessionId?.replace("MAINT", "") // dunno what's this
if (sessionId == null) { if (sessionId == null) {

View File

@ -37,19 +37,19 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
else if (data.portalRefreshToken != null) { else if (data.portalRefreshToken != null) {
if (data.fakeLogin) { if (data.fakeLogin) {
data.app.cookieJar.clearForDomain("librus.szkolny.eu") data.app.cookieJar.clear("librus.szkolny.eu")
} }
else { else {
data.app.cookieJar.clearForDomain("portal.librus.pl") data.app.cookieJar.clear("portal.librus.pl")
} }
accessToken(null, data.portalRefreshToken) accessToken(null, data.portalRefreshToken)
} }
else { else {
if (data.fakeLogin) { if (data.fakeLogin) {
data.app.cookieJar.clearForDomain("librus.szkolny.eu") data.app.cookieJar.clear("librus.szkolny.eu")
} }
else { else {
data.app.cookieJar.clearForDomain("portal.librus.pl") data.app.cookieJar.clear("portal.librus.pl")
} }
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL) authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
} }

View File

@ -8,7 +8,6 @@ import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
@ -30,17 +29,11 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
} }
if (data.isSynergiaLoginValid()) { if (data.isSynergiaLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", data.synergiaSessionId)
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
onSuccess() onSuccess()
} }
else { else {
data.app.cookieJar.clearForDomain("synergia.librus.pl") data.app.cookieJar.clear("synergia.librus.pl")
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) { if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) {
loginWithApi() loginWithApi()
} }
@ -92,7 +85,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
} }
if (location?.endsWith("centrum_powiadomien") == true) { if (location?.endsWith("centrum_powiadomien") == true) {
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID") val sessionId = data.app.cookieJar.get("synergia.librus.pl", "DZIENNIKSID")
if (sessionId == null) { if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID)
.withResponse(response) .withResponse(response)
@ -117,7 +110,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
} }
} }
data.app.cookieJar.clearForDomain("synergia.librus.pl") data.app.cookieJar.clear("synergia.librus.pl")
Request.builder() Request.builder()
.url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien") .url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien")
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)

View File

@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.FileCallbackHandler import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -26,6 +25,23 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
val profile val profile
get() = data.profile get() = data.profile
/* TODO
add error handling:
<!-- CONTENT -->
<div id="content">
<a name="tresc"></a>
<h1>Ważna informacja!</h1>
<div class="okienko_informacyjne">
Korzystasz z hasła, które zostało wygenerowane automatycznie.<hr/>
Pamiętaj, aby je zmienić oraz uzupełnić dane w swoim profilu.<br/><br/>
Obie te operacje można wykonać uruchamiając opcję
<a href="https://poznan.mobidziennik.pl/dziennik/edytujprofil" title="Edycja profilu, możliwość zmiany hasła">Moje konto->Edycja profilu</a>.
</div> </div>
*/
fun webGet( fun webGet(
tag: String, tag: String,
endpoint: String, endpoint: String,
@ -65,6 +81,12 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
return return
} }
if (text.contains("<h2>Problemy z wydajnością</h2>")) {
data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM)
.withResponse(response))
return
}
try { try {
onSuccess(text) onSuccess(text)
} catch (e: Exception) { } catch (e: Exception) {
@ -82,18 +104,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
} }
} }
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
Cookie.Builder() data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
.name(data.webSessionKey!!)
.value(data.webSessionValue!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name("SERVERID")
.value(data.webServerId!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build()
))
Request.builder() Request.builder()
.url(url) .url(url)
@ -164,18 +176,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
} }
} }
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
Cookie.Builder() data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
.name(data.webSessionKey!!)
.value(data.webSessionValue!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name("SERVERID")
.value(data.webServerId!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build()
))
Request.builder() Request.builder()
.url(url) .url(url)

View File

@ -51,17 +51,16 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
val eventObject = Event( val eventObject = Event(
data.profileId, profileId = data.profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId)
teamId)
data.eventList.add(eventObject) data.eventList.add(eventObject)
data.metadataList.add( data.metadataList.add(

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
import android.text.Html
import androidx.core.util.contains import androidx.core.util.contains
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
@ -25,22 +26,21 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List<String>) {
val id = cols[0].toLong() val id = cols[0].toLong()
val teacherId = cols[7].toLong() val teacherId = cols[7].toLong()
val subjectId = cols[6].toLong() val subjectId = cols[6].toLong()
val topic = cols[1] val topic = Html.fromHtml(cols[1])?.toString() ?: ""
val eventDate = Date.fromYmd(cols[2]) val eventDate = Date.fromYmd(cols[2])
val startTime = Time.fromYmdHm(cols[3]) val startTime = Time.fromYmdHm(cols[3])
val eventObject = Event( val eventObject = Event(
data.profileId, profileId = data.profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId)
teamId)
data.eventList.add(eventObject) data.eventList.add(eventObject)
data.metadataList.add( data.metadataList.add(

View File

@ -44,7 +44,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List<String>?
val studentId = cols[1].toInt() val studentId = cols[1].toInt()
val teamId = cols[2].toLong() val teamId = cols[2].toLong()
val studentNumber = cols[4].toInt() val studentNumber = cols[4].toIntOrNull() ?: -1
if (studentId != data.studentId) if (studentId != data.studentId)
continue continue

View File

@ -61,26 +61,25 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik,
val title = event.getString("title") val title = event.getString("title")
val comment = event.getString("comment") val comment = event.getString("comment")
var topic = title var topic = title ?: ""
if (title != comment) { if (title != comment) {
topic += "\n" + comment topic += "\n" + comment
} }
if (id == -1L) { if (id == -1L) {
id = crc16(topic?.toByteArray()).toLong() id = crc16(topic.toByteArray()).toLong()
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, null, date = eventDate, time = null,
topic, topic = topic,
-1, color = null,
eventType, type = eventType,
false, teacherId = -1,
-1, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -118,7 +118,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik,
// this needs to be at the end // this needs to be at the end
message.apply { message.apply {
this.body = body.html().replace("\n", "<br>") this.body = body.html()
clearAttachments() clearAttachments()
content.select("ul li").map { it.select("a").first() }.forEach { content.select("ul li").map { it.select("a").first() }.forEach {

View File

@ -32,15 +32,15 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
val doc = Jsoup.parse(text) val doc = Jsoup.parse(text)
val listElement = doc.getElementsByClass("spis").first() val listElement = doc.getElementsByClass("spis")?.first()
if (listElement == null) { if (listElement == null) {
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL, 7*DAY) data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL, 7*DAY)
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL) onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL)
return@webGet return@webGet
} }
val list = listElement.getElementsByClass("podswietl") val list = listElement.getElementsByClass("podswietl")
for (item in list) { list?.forEach { item ->
val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: continue val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: return@forEach
val subjectEl = item.select("td:eq(0) div").first() val subjectEl = item.select("td:eq(0) div").first()
val subject = subjectEl.text() val subject = subjectEl.text()

View File

@ -36,9 +36,9 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik,
val doc = Jsoup.parse(text) val doc = Jsoup.parse(text)
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl") val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
for (item in list) { list?.forEach { item ->
val id = item.attr("rel").toLongOrNull() ?: continue val id = item.attr("rel").toLongOrNull() ?: return@forEach
val subjectEl = item.select("td:eq(0)").first() val subjectEl = item.select("td:eq(0)").first()
var hasAttachments = false var hasAttachments = false

View File

@ -40,9 +40,9 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik,
val doc = Jsoup.parse(text) val doc = Jsoup.parse(text)
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl") val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
for (item in list) { list?.forEach { item ->
val id = item.attr("rel").toLongOrNull() ?: continue val id = item.attr("rel").toLongOrNull() ?: return@forEach
val subjectEl = item.select("td:eq(0)").first() val subjectEl = item.select("td:eq(0)").first()
var hasAttachments = false var hasAttachments = false

View File

@ -26,7 +26,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
} }
else { else {
if (data.loginServerName.isNotNullNorEmpty() && data.loginUsername.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) { if (data.loginServerName.isNotNullNorEmpty() && data.loginUsername.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
data.app.cookieJar.clearForDomain(data.loginServerName + ".mobidziennik.pl") data.app.cookieJar.clear("${data.loginServerName}.mobidziennik.pl")
loginWithCredentials() loginWithCredentials()
} }
else { else {
@ -58,10 +58,10 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
} }
} }
val cookies = data.app.cookieJar.getForDomain("${data.loginServerName}.mobidziennik.pl") val cookies = data.app.cookieJar.getAll("${data.loginServerName}.mobidziennik.pl")
val cookie = cookies.singleOrNull { it.name().length > 32 } val cookie = cookies.entries.firstOrNull { it.key.length > 32 }
val sessionKey = cookie?.name() val sessionKey = cookie?.key
val sessionId = cookie?.value() val sessionId = cookie?.value
if (sessionId == null) { if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID) data.error(ApiError(TAG, ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID)
.withResponse(response) .withResponse(response)
@ -71,7 +71,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
data.webSessionKey = sessionKey data.webSessionKey = sessionKey
data.webSessionValue = sessionId data.webSessionValue = sessionId
data.webServerId = data.app.cookieJar.getCookie("${data.loginServerName}.mobidziennik.pl", "SERVERID") data.webServerId = data.app.cookieJar.get("${data.loginServerName}.mobidziennik.pl", "SERVERID")
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */ data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess() onSuccess()
} }

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.template package pl.szczodrzynski.edziennik.data.api.edziennik.template
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.currentTimeUnix import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API
@ -28,13 +27,7 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
loginMethods.clear() loginMethods.clear()
if (isWebLoginValid()) { if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_TEMPLATE_WEB loginMethods += LOGIN_METHOD_TEMPLATE_WEB
app.cookieJar.saveFromResponse(null, listOf( app.cookieJar.set("eregister.example.com", "AuthCookie", webCookie)
Cookie.Builder()
.name("AuthCookie")
.value(webCookie!!)
.domain("eregister.example.com")
.secure().httpOnly().build()
))
} }
if (isApiLoginValid()) if (isApiLoginValid())
loginMethods += LOGIN_METHOD_TEMPLATE_API loginMethods += LOGIN_METHOD_TEMPLATE_API

View File

@ -4,12 +4,11 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.template.login package pl.szczodrzynski.edziennik.data.api.edziennik.template.login
import okhttp3.Cookie import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_MISSING import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_MISSING
import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.currentTimeUnix
class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) { class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
companion object { companion object {
@ -23,17 +22,11 @@ class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
} }
if (data.isWebLoginValid()) { if (data.isWebLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf( data.app.cookieJar.set("eregister.example.com", "AuthCookie", data.webCookie)
Cookie.Builder()
.name("AuthCookie")
.value(data.webCookie!!)
.domain("eregister.example.com")
.secure().httpOnly().build()
))
onSuccess() onSuccess()
} }
else { else {
data.app.cookieJar.clearForDomain("eregister.example.com") data.app.cookieJar.clear("eregister.example.com")
if (/*data.webLogin != null && data.webPassword != null && */true) { if (/*data.webLogin != null && data.webPassword != null && */true) {
loginWithCredentials() loginWithCredentials()
} }

View File

@ -10,7 +10,10 @@ import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.data.api.models.Data import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.values
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
@ -26,6 +29,25 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
} }
} }
init {
// during the first sync `profile.studentClassName` is already set
if (teamList.values().none { it.type == Team.TYPE_CLASS }) {
profile?.studentClassName?.also { name ->
val id = Utils.crc16(name.toByteArray()).toLong()
val teamObject = Team(
profileId,
id,
name,
Team.TYPE_CLASS,
"$schoolName:$name",
-1
)
teamList.put(id, teamObject)
}
}
}
override fun generateUserCode() = "$schoolName:$studentId" override fun generateUserCode() = "$schoolName:$studentId"
/** /**
@ -188,11 +210,11 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
"SZ9" -> "http://hack.szkolny.eu" "SZ9" -> "http://hack.szkolny.eu"
else -> null else -> null
} }
return if (url != null) "$url/$symbol" else loginStore.getLoginData("apiUrl", null) return if (url != null) "$url/$symbol/" else loginStore.getLoginData("apiUrl", null)
} }
val fullApiUrl: String? val fullApiUrl: String?
get() { get() {
return "$apiUrl/$schoolSymbol" return "$apiUrl$schoolSymbol/"
} }
} }

View File

@ -13,8 +13,6 @@ import io.github.wulkanowy.signer.android.signContent
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.util.* import java.util.*
@ -38,26 +36,10 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
baseUrl: Boolean = false, baseUrl: Boolean = false,
onSuccess: (json: JsonObject, response: Response?) -> Unit onSuccess: (json: JsonObject, response: Response?) -> Unit
) { ) {
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}/$endpoint" val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
d(tag, "Request: Vulcan/Api - $url") d(tag, "Request: Vulcan/Api - $url")
if (data.teamList.size() == 0) {
data.profile?.studentClassName?.also { name ->
val id = Utils.crc16(name.toByteArray()).toLong()
val teamObject = Team(
profileId,
id,
name,
Team.TYPE_CLASS,
"${data.schoolName}:$name",
-1
)
data.teamList.put(id, teamObject)
}
}
val finalPayload = JsonObject() val finalPayload = JsonObject()
parameters.map { (name, value) -> parameters.map { (name, value) ->
when (value) { when (value) {

View File

@ -72,17 +72,16 @@ class VulcanApiEvents(override val data: DataVulcan,
val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1 val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId
teamId
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View File

@ -86,31 +86,36 @@ class VulcanApiTimetable(override val data: DataVulcan,
data.teamList[id] = team data.teamList[id] = team
} }
team.id team.id
} ?: data.studentClassId.toLong() } ?: data.teamClass?.id ?: -1
val subjectId = lesson.getLong("IdPrzedmiot")?.let { val subjectId = lesson.getLong("IdPrzedmiot").let { id ->
when (it) { // get the specified subject name
0L -> { val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id val condition = when (id) {
?: { // "special" subject - e.g. one time classes, a trip, etc.
/** 0L -> { subject: Subject -> subject.longName == subjectName }
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST // normal subject, check if it exists
*/ else -> { subject: Subject -> subject.id == id }
val subjectObject = Subject(
profileId,
-1 * crc16(subjectName.toByteArray()).toLong(),
subjectName,
subjectName
)
data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id
}.invoke()
}
else -> it
} }
data.subjectList.singleOrNull(condition)?.id ?: {
/**
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
*/
val subjectObject = Subject(
profileId,
if (id == null || id == 0L)
-1 * crc16(subjectName.toByteArray()).toLong()
else
id,
subjectName,
subjectName
)
data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id
}()
} }
val lessonObject = Lesson(profileId, -1).apply { val lessonObject = Lesson(profileId, -1).apply {

View File

@ -137,7 +137,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
} }
Request.builder() Request.builder()
.url("${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE") .url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
.userAgent(VULCAN_API_USER_AGENT) .userAgent(VULCAN_API_USER_AGENT)
.addHeader("RequestMobileType", "RegisterDevice") .addHeader("RequestMobileType", "RegisterDevice")
.addParameter("PIN", data.apiPin) .addParameter("PIN", data.apiPin)

View File

@ -12,10 +12,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
@ -28,7 +25,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.md5
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -80,7 +76,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
withContext(Dispatchers.Default) { block() } withContext(Dispatchers.Default) { block() }
} }
catch (e: Exception) { catch (e: Exception) {
errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show() errorSnackbar.addError(e.toApiError(TAG)).show()
null null
} }
} }
@ -91,7 +87,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
catch (e: Exception) { catch (e: Exception) {
ErrorDetailsDialog( ErrorDetailsDialog(
activity, activity,
listOf(ApiError.fromThrowable(TAG, e)), listOf(e.toApiError(TAG)),
R.string.error_occured R.string.error_occured
) )
null null
@ -160,7 +156,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> { fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>, lastSyncTime: Long): List<EventFull> {
val teams = app.db.teamDao().allNow val teams = app.db.teamDao().allNow
val response = api.serverSync(ServerSyncRequest( val response = api.serverSync(ServerSyncRequest(
@ -185,19 +181,31 @@ class SzkolnyApi(val app: App) : CoroutineScope {
} }
} }
}, },
lastSync = lastSyncTime,
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) } notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
)).execute() )).execute()
parseResponse(response) val (events, hasBrowsers) = parseResponse(response)
val events = mutableListOf<EventFull>() hasBrowsers?.let {
app.config.sync.webPushEnabled = it
}
response.body()?.data?.events?.forEach { event -> val eventList = mutableListOf<EventFull>()
events.forEach { event ->
// skip blacklisted events
if (event.id in blacklistedIds) if (event.id in blacklistedIds)
return@forEach return@forEach
// force nullable non-negative colors
if (event.color == -1)
event.color = null
// create the event for every matching team and profile
teams.filter { it.code == event.teamCode }.onEach { team -> teams.filter { it.code == event.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
events.add(EventFull(event).apply { eventList += EventFull(event).apply {
profileId = team.profileId profileId = team.profileId
teamId = team.id teamId = team.id
addedManually = true addedManually = true
@ -205,11 +213,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
notified = profile.empty notified = profile.empty
if (profile.userCode == event.sharedBy) sharedBy = "self" if (profile.userCode == event.sharedBy) sharedBy = "self"
}) }
} }
} }
return events return eventList
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -253,9 +261,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
browserId = browserId, browserId = browserId,
pairToken = pairToken pairToken = pairToken
)).execute() )).execute()
parseResponse(response)
return response.body()?.data?.browsers ?: emptyList() return parseResponse(response).browsers
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -265,9 +272,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
device = getDevice(), device = getDevice(),
action = "listBrowsers" action = "listBrowsers"
)).execute() )).execute()
parseResponse(response)
return response.body()?.data?.browsers ?: emptyList() return parseResponse(response).browsers
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -278,9 +284,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
action = "unpairBrowser", action = "unpairBrowser",
browserId = browserId browserId = browserId
)).execute() )).execute()
parseResponse(response)
return response.body()?.data?.browsers ?: emptyList() return parseResponse(response).browsers
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -307,9 +312,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
@Throws(Exception::class) @Throws(Exception::class)
fun getUpdate(channel: String): List<Update> { fun getUpdate(channel: String): List<Update> {
val response = api.updates(channel).execute() val response = api.updates(channel).execute()
parseResponse(response) return parseResponse(response)
return response.body()?.data ?: emptyList()
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -321,8 +324,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
targetDeviceId = targetDeviceId, targetDeviceId = targetDeviceId,
text = text text = text
)).execute() )).execute()
val data = parseResponse(response)
return data.message return parseResponse(response).message
} }
} }

View File

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

View File

@ -12,7 +12,8 @@ data class EventShareRequest (
val action: String = "event", val action: String = "event",
val sharedByName: String, /* If null, the server shows an error */
val sharedByName: String?,
val shareTeamCode: String? = null, val shareTeamCode: String? = null,
val unshareTeamCode: String? = null, val unshareTeamCode: String? = null,
val requesterName: String? = null, val requesterName: String? = null,

View File

@ -11,6 +11,8 @@ data class ServerSyncRequest(
val userCodes: List<String>, val userCodes: List<String>,
val users: List<User>? = null, val users: List<User>? = null,
val lastSync: Long,
val notifications: List<Notification>? = null val notifications: List<Notification>? = null
) { ) {
data class User( data class User(

View File

@ -6,4 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
data class ServerSyncResponse(val events: List<EventFull>) data class ServerSyncResponse(
val events: List<EventFull>,
val hasBrowsers: Boolean? = null
)

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.models.Date
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) { class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
companion object { companion object {
@ -24,26 +25,27 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
* *
* @return a number of events inserted to DB, possibly needing a notification * @return a number of events inserted to DB, possibly needing a notification
*/ */
fun run(): Int { fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int {
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived } val blacklistedIds = app.db.eventDao().blacklistedIds
if (profiles.isNotEmpty()) { val events = api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime)
val blacklistedIds = app.db.eventDao().blacklistedIds;
val events = api.getEvents(profiles, notifications, blacklistedIds)
if (events.isNotEmpty()) { app.config.sync.lastAppSync = System.currentTimeMillis()
app.db.metadataDao().addAllIgnore(events.map { event ->
Metadata( if (events.isNotEmpty()) {
event.profileId, val today = Date.getToday()
Metadata.TYPE_EVENT, app.db.metadataDao().addAllIgnore(events.map { event ->
event.id, val isPast = event.date < today
event.seen, Metadata(
event.notified, event.profileId,
event.addedDate Metadata.TYPE_EVENT,
) event.id,
}) isPast || markAsSeen || event.seen,
return app.db.eventDao().addAll(events).size isPast || markAsSeen || event.notified,
} event.addedDate
)
})
return app.db.eventDao().addAll(events).size
} }
return 0; return 0;
} }
} }

View File

@ -34,6 +34,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
announcementNotifications() announcementNotifications()
messageNotifications() messageNotifications()
luckyNumberNotifications() luckyNumberNotifications()
teacherAbsenceNotifications()
} }
private fun timetableNotifications() { private fun timetableNotifications() {
@ -58,7 +59,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
} }
private fun eventNotifications() { private fun eventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.eventDate >= today }) { for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today }) {
val text = if (event.type == Event.TYPE_HOMEWORK) val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString( app.getString(
if (event.subjectLongName.isNullOrEmpty()) if (event.subjectLongName.isNullOrEmpty())
@ -66,7 +67,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
else else
R.string.notification_homework_format, R.string.notification_homework_format,
event.subjectLongName, event.subjectLongName,
event.eventDate.formattedString event.date.formattedString
) )
else else
app.getString( app.getString(
@ -74,8 +75,8 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
R.string.notification_event_no_subject_format R.string.notification_event_no_subject_format
else else
R.string.notification_event_format, R.string.notification_event_format,
event.typeName, event.typeName ?: "wydarzenie",
event.eventDate.formattedString, event.date.formattedString,
event.subjectLongName event.subjectLongName
) )
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
@ -88,17 +89,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }
fun sharedEventNotifications() { fun sharedEventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.eventDate >= today && it.sharedBy != null }) { for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today && it.sharedBy != null }) {
val text = app.getString( val text = app.getString(
R.string.notification_shared_event_format, R.string.notification_shared_event_format,
event.sharedByName, event.sharedByName,
event.typeName ?: "wydarzenie", event.typeName ?: "wydarzenie",
event.eventDate.formattedString, event.date.formattedString,
event.topic event.topic
) )
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
@ -111,7 +112,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }
@ -274,4 +275,23 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
) )
} }
} }
private fun teacherAbsenceNotifications() {
for (teacherAbsence in app.db.teacherAbsenceDao().getNotNotifiedNow()) {
val message = app.getString(
R.string.notification_teacher_absence_new_format,
teacherAbsence.teacherFullName
)
notifications += Notification(
id = Notification.buildId(teacherAbsence.profileId, Notification.TYPE_TEACHER_ABSENCE, teacherAbsence.id),
title = app.getNotificationTitle(Notification.TYPE_TEACHER_ABSENCE),
text = message,
type = Notification.TYPE_TEACHER_ABSENCE,
profileId = teacherAbsence.profileId,
profileName = profiles.singleOrNull { it.id == teacherAbsence.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA,
addedDate = teacherAbsence.addedDate
).addExtra("eventDate", teacherAbsence.dateFrom.value.toLong())
}
}
} }

View File

@ -31,16 +31,23 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
val notifications = Notifications(app, notificationList, profiles) val notifications = Notifications(app, notificationList, profiles)
notifications.run() notifications.run()
val shouldAppSync = notificationList.isNotEmpty() || (System.currentTimeMillis() - app.config.lastAppSync > 24*HOUR*1000) val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
// do an AppSync every 24 hours, or if WebPush has a notification // App Sync conditions:
// - every 24 hours && any profile is registered
// - if there are new notifications && any browser is paired
val shouldAppSync =
System.currentTimeMillis() - app.config.sync.lastAppSync > 24*HOUR*1000
&& appSyncProfiles.isNotEmpty()
|| notificationList.isNotEmpty()
&& app.config.sync.webPushEnabled
if (shouldAppSync) { if (shouldAppSync) {
// send notifications to web push, get shared events // send notifications to web push, get shared events
val addedEvents = AppSync(app, notificationList, profiles, api).run() val addedEvents = AppSync(app, notificationList, appSyncProfiles, api).run(app.config.sync.lastAppSync)
if (addedEvents > 0) { if (addedEvents > 0) {
// create notifications for shared events (not present before app sync) // create notifications for shared events (not present before app sync)
notifications.sharedEventNotifications() notifications.sharedEventNotifications()
} }
app.config.lastAppSync = System.currentTimeMillis()
} }
d(TAG, "Created ${notificationList.count()} notifications.") d(TAG, "Created ${notificationList.count()} notifications.")

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class, LibrusLesson::class,
TimetableManual::class, TimetableManual::class,
Metadata::class Metadata::class
], version = 79) ], version = 81)
@TypeConverters( @TypeConverters(
ConverterTime::class, ConverterTime::class,
ConverterDate::class, ConverterDate::class,
@ -164,7 +164,9 @@ abstract class AppDb : RoomDatabase() {
Migration76(), Migration76(),
Migration77(), Migration77(),
Migration78(), Migration78(),
Migration79() Migration79(),
Migration80(),
Migration81()
).allowMainThreadQueries().build() ).allowMainThreadQueries().build()
} }
} }

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
*/
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.RawQuery
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
@Dao
interface BaseDao<T, F> {
@RawQuery
fun getRaw(query: SupportSQLiteQuery): LiveData<List<F>>
fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query))
@RawQuery
fun getRawNow(query: SupportSQLiteQuery): List<F>
fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query))
@RawQuery
fun getOneNow(query: SupportSQLiteQuery): F?
fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query))
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(item: T): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(items: List<T>): LongArray
fun clear(profileId: Int)
}

View File

@ -1,186 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Event;
import pl.szczodrzynski.edziennik.data.db.full.EventFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_EVENT;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LESSON_CHANGE;
@Dao
public abstract class EventDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long add(Event event);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long[] addAll(List<Event> eventList);
@Query("DELETE FROM events WHERE profileId = :profileId")
public abstract void clear(int profileId);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
public abstract void remove(int profileId, long id);
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
public abstract void removeMetadata(int profileId, int thingType, long thingId);
@Transaction
public void remove(int profileId, long type, long id) {
remove(profileId, id);
removeMetadata(profileId, type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, id);
}
@Transaction
public void remove(Event event) {
remove(event.profileId, event.type, event.id);
}
@Transaction
public void remove(int profileId, Event event) {
remove(profileId, event.type, event.id);
}
@Query("DELETE FROM events WHERE teamId = :teamId AND eventId = :id")
public abstract void removeByTeamId(long teamId, long id);
@RawQuery(observedEntities = {Event.class})
abstract LiveData<List<EventFull>> getAll(SupportSQLiteQuery query);
public LiveData<List<EventFull>> getAll(int profileId, String filter, String limit) {
String query = "SELECT \n" +
"*, \n" +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
"eventTypes.eventTypeName AS typeName,\n" +
"eventTypes.eventTypeColor AS typeColor\n" +
"FROM events\n" +
"LEFT JOIN subjects USING(profileId, subjectId)\n" +
"LEFT JOIN teachers USING(profileId, teacherId)\n" +
"LEFT JOIN teams USING(profileId, teamId)\n" +
"LEFT JOIN eventTypes USING(profileId, eventType)\n" +
"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 "+limit;
Log.d("DB", query);
return getAll(new SimpleSQLiteQuery(query));
}
public LiveData<List<EventFull>> getAll(int profileId) {
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, "");
}
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String 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()+"'", "");
}
public List<EventFull> getAllByDateNow(int profileId, @NonNull Date date) {
return getAllNow(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
}
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()+"'", "");
}
public LiveData<List<EventFull>> getAllNearest(int profileId, @NonNull Date today, int limit) {
return getAll(profileId, "eventDate >= '"+today.getStringY_m_d()+"'", "LIMIT "+limit);
}
@RawQuery
abstract List<EventFull> getAllNow(SupportSQLiteQuery query);
public List<EventFull> getAllNow(int profileId, String filter) {
return getAllNow(new SimpleSQLiteQuery("SELECT \n" +
"*, \n" +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
"eventTypes.eventTypeName AS typeName,\n" +
"eventTypes.eventTypeColor AS typeColor\n" +
"FROM events \n" +
"LEFT JOIN subjects USING(profileId, subjectId)\n" +
"LEFT JOIN teachers USING(profileId, teacherId)\n" +
"LEFT JOIN teams USING(profileId, teamId)\n" +
"LEFT JOIN eventTypes USING(profileId, eventType)\n" +
"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 eventStartTime, addedDate ASC"));
}
public List<EventFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds(int profileId);
@Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds();
@Query("SELECT " +
"*, " +
"eventTypes.eventTypeName AS typeName, " +
"eventTypes.eventTypeColor AS typeColor " +
"FROM events " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN eventTypes USING(profileId, eventType) " +
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = events.profileId " +
"WHERE events.eventBlacklisted = 0 AND notified = 0 " +
"GROUP BY eventId " +
"ORDER BY addedDate ASC")
public abstract List<EventFull> getNotNotifiedNow();
public EventFull getByIdNow(int profileId, long eventId) {
List<EventFull> eventList = getAllNow(profileId, "eventId = "+eventId);
return eventList.size() == 0 ? null : eventList.get(0);
}
@Query("UPDATE events SET eventAddedManually = 1 WHERE profileId = :profileId AND eventDate < :date")
public abstract void convertOlderToManual(int profileId, Date date);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0")
public abstract void removeNotManual(int profileId);
@RawQuery
abstract long removeFuture(SupportSQLiteQuery query);
@Transaction
public void removeFuture(int profileId, Date todayDate, String filter) {
removeFuture(new SimpleSQLiteQuery("DELETE FROM events WHERE profileId = " + profileId
+ " AND eventAddedManually = 0 AND eventDate >= '" + todayDate.getStringY_m_d() + "'" +
" AND " + filter));
}
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
public abstract void removeFutureWithType(int profileId, Date todayDate, long type);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
public abstract void removeFutureExceptType(int profileId, Date todayDate, long exceptType);
@Transaction
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Long> exceptTypes) {
removeFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'));
}
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = "+TYPE_EVENT+" OR thingType = "+TYPE_LESSON_CHANGE+" OR thingType = "+TYPE_HOMEWORK+") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
public abstract void setSeenByDate(int profileId, Date date, boolean seen);
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
public abstract void setBlacklisted(int profileId, long eventId, boolean blacklisted);
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@Dao
abstract class EventDao : BaseDao<Event, EventFull> {
companion object {
private const val QUERY = """
SELECT
*,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
eventTypes.eventTypeName AS typeName,
eventTypes.eventTypeColor AS typeColor
FROM events
LEFT JOIN subjects USING(profileId, subjectId)
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teams USING(profileId, teamId)
LEFT JOIN eventTypes USING(profileId, eventType)
LEFT JOIN metadata ON eventId = thingId AND (thingType = ${Metadata.TYPE_EVENT} OR thingType = ${Metadata.TYPE_HOMEWORK}) AND metadata.profileId = events.profileId
"""
private const val ORDER_BY = """GROUP BY eventId ORDER BY eventDate, eventTime, addedDate ASC"""
private const val NOT_BLACKLISTED = """events.eventBlacklisted = 0"""
}
//abstract fun queryRaw(query: SupportSQLiteQuery)
//private fun queryRaw(query: String) = queryRaw(SimpleSQLiteQuery(query))
@Query("DELETE FROM events WHERE profileId = :profileId")
abstract override fun clear(profileId: Int)
/*fun update(event: Event) =
queryRaw("""UPDATE events SET
eventDate = '${event.date.stringY_m_d}',
eventTime = ${event.time?.stringValue},
eventTopic = '${event.topic}'""")*/
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
abstract fun remove(profileId: Int, id: Long)
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
abstract fun removeMetadata(profileId: Int, thingType: Int, thingId: Long)
@Transaction
open fun remove(profileId: Int, type: Long, id: Long) {
remove(profileId, id)
removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id)
}
@Transaction
open fun remove(event: Event) {
remove(event.profileId, event.type, event.id)
}
@Transaction
open fun remove(profileId: Int, event: Event) {
remove(profileId, event.type, event.id)
}
@RawQuery(observedEntities = [Event::class])
abstract override fun getRaw(query: SupportSQLiteQuery): LiveData<List<EventFull>>
fun getAll(profileId: Int) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
fun getAllByType(profileId: Int, type: Long, filter: String = "1") =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventType = $type AND $filter $ORDER_BY")
fun getAllByDate(profileId: Int, date: Date) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
fun getAllByDateTime(profileId: Int, date: Date, time: Time) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' AND eventTime = ${time.stringValue}")
fun getAllNearest(profileId: Int, today: Date, limit: Int) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate >= '${today.stringY_m_d}' $ORDER_BY LIMIT $limit")
fun getAllNow(profileId: Int) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
fun getNotNotifiedNow() =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND notified = 0 $ORDER_BY")
fun getNotNotifiedNow(profileId: Int) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND notified = 0 $ORDER_BY")
fun getAllByDateNow(profileId: Int, date: Date) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
fun getByIdNow(profileId: Int, id: Long) =
getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id")
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
abstract fun getBlacklistedIds(profileId: Int): List<Long>
@get:Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
abstract val blacklistedIds: List<Long>
@Query("UPDATE events SET eventAddedManually = 1 WHERE profileId = :profileId AND eventDate < :date")
abstract fun convertOlderToManual(profileId: Int, date: Date?)
@Query("DELETE FROM events WHERE teamId = :teamId AND eventId = :id")
abstract fun removeByTeamId(teamId: Long, id: Long)
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0")
abstract fun removeNotManual(profileId: Int)
@RawQuery
abstract fun removeFuture(query: SupportSQLiteQuery?): Long
@Transaction
open fun removeFuture(profileId: Int, todayDate: Date, filter: String) {
removeFuture(SimpleSQLiteQuery("DELETE FROM events WHERE profileId = " + profileId
+ " AND eventAddedManually = 0 AND eventDate >= '" + todayDate.stringY_m_d + "'" +
" AND " + filter))
}
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
abstract fun removeFutureWithType(profileId: Int, todayDate: Date, type: Long)
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
abstract fun removeFutureExceptType(profileId: Int, todayDate: Date, exceptType: Long)
@Transaction
open fun removeFutureExceptTypes(profileId: Int, todayDate: Date, exceptTypes: List<Long>) {
removeFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'))
}
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = " + Metadata.TYPE_EVENT + " OR thingType = " + Metadata.TYPE_LESSON_CHANGE + " OR thingType = " + Metadata.TYPE_HOMEWORK + ") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
abstract fun setSeenByDate(profileId: Int, date: Date, seen: Boolean)
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
abstract fun setBlacklisted(profileId: Int, eventId: Long, blacklisted: Boolean)
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
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 java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.EventType;
@Dao
public interface EventTypeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void add(EventType gradeCategory);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addAll(List<EventType> gradeCategoryList);
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
void clear(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
EventType getByIdNow(int profileId, long typeId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
LiveData<List<EventType>> getAll(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
List<EventType> getAllNow(int profileId);
@Query("SELECT * FROM eventTypes")
List<EventType> getAllNow();
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.EventType
@Dao
abstract class EventTypeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun add(eventType: EventType)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun addAll(eventTypeList: List<EventType>)
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
abstract fun clear(profileId: Int)
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
abstract fun getByIdNow(profileId: Int, typeId: Long): EventType?
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
abstract fun getAll(profileId: Int): LiveData<List<EventType>>
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
abstract fun getAllNow(profileId: Int): List<EventType>
@get:Query("SELECT * FROM eventTypes")
abstract val allNow: List<EventType>
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
val typeList = listOf(
EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK),
EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT),
EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM),
EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ),
EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY),
EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT),
EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING),
EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION),
EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING),
EventType(profileId, TYPE_CLASS_EVENT, context.getString(R.string.event_class_event), COLOR_CLASS_EVENT),
EventType(profileId, TYPE_INFORMATION, context.getString(R.string.event_information), COLOR_INFORMATION)
)
addAll(typeList)
return typeList
}
}

View File

@ -78,8 +78,8 @@ public abstract class MetadataDao {
} }
} }
if (o instanceof Event) { if (o instanceof Event) {
if (add(new Metadata(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen, false, 0)) == -1) { if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false, 0)) == -1) {
updateSeen(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen); updateSeen(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen);
} }
} }
if (o instanceof LessonFull) { if (o instanceof LessonFull) {
@ -117,8 +117,8 @@ public abstract class MetadataDao {
} }
} }
if (o instanceof Event) { if (o instanceof Event) {
if (add(new Metadata(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, false, notified, 0)) == -1) { if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified, 0)) == -1) {
updateNotified(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, notified); updateNotified(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified);
} }
} }
if (o instanceof LessonFull) { if (o instanceof LessonFull) {
@ -141,9 +141,9 @@ public abstract class MetadataDao {
@Transaction @Transaction
public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) { public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) {
if (o != null) { if (o != null) {
if (add(new Metadata(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen, notified, addedDate)) == -1) { if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified, addedDate)) == -1) {
updateSeen(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen); updateSeen(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen);
updateNotified(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, notified); updateNotified(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), notified);
} }
} }
} }

View File

@ -41,7 +41,7 @@ interface ProfileDao {
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int> fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
@get:Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId") @get:Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
val profilesForSyncNow: List<Profile> val profilesForFirebaseNow: List<Profile>
@get:Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId") @get:Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
val idsForSyncNow: List<Int> val idsForSyncNow: List<Int>

View File

@ -49,6 +49,17 @@ interface TeacherAbsenceDao {
"AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo")
fun getAllByDateNow(profileId: Int, date: Date): List<TeacherAbsenceFull> fun getAllByDateNow(profileId: Int, date: Date): List<TeacherAbsenceFull>
@Query("""
SELECT *,
teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName
FROM teacherAbsence
LEFT JOIN teachers USING (profileId, teacherId)
LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = ${Metadata.TYPE_TEACHER_ABSENCE}
AND teachers.profileId = metadata.profileId WHERE metadata.notified = 0
ORDER BY addedDate DESC
""")
fun getNotNotifiedNow(): List<TeacherAbsenceFull>
@Query("DELETE FROM teacherAbsence WHERE profileId = :profileId") @Query("DELETE FROM teacherAbsence WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
} }

View File

@ -1,161 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.entity;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import java.util.Calendar;
import pl.szczodrzynski.edziennik.data.db.full.EventFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
@Entity(tableName = "events",
primaryKeys = {"profileId", "eventId"},
indices = {@Index(value = {"profileId", "eventDate", "eventStartTime"}), @Index(value = {"profileId", "eventType"})})
public class Event {
public int profileId;
@ColumnInfo(name = "eventId")
public long id;
@ColumnInfo(name = "eventDate")
public Date eventDate;
@ColumnInfo(name = "eventStartTime")
@Nullable
public Time startTime; // null for allDay
@ColumnInfo(name = "eventTopic")
public String topic;
@ColumnInfo(name = "eventColor")
public int color = -1;
public static final long TYPE_UNDEFINED = -2;
public static final long TYPE_HOMEWORK = -1;
public static final long TYPE_DEFAULT = 0;
public static final long TYPE_EXAM = 1;
public static final long TYPE_SHORT_QUIZ = 2;
public static final long TYPE_ESSAY = 3;
public static final long TYPE_PROJECT = 4;
public static final long TYPE_PT_MEETING = 5;
public static final long TYPE_EXCURSION = 6;
public static final long TYPE_READING = 7;
public static final long TYPE_CLASS_EVENT = 8;
public static final long TYPE_INFORMATION = 9;
public static final long TYPE_TEACHER_ABSENCE = 10;
public static final int COLOR_HOMEWORK = 0xff795548;
public static final int COLOR_DEFAULT = 0xffffc107;
public static final int COLOR_EXAM = 0xfff44336;
public static final int COLOR_SHORT_QUIZ = 0xff76ff03;
public static final int COLOR_ESSAY = 0xFF4050B5;
public static final int COLOR_PROJECT = 0xFF673AB7;
public static final int COLOR_PT_MEETING = 0xff90caf9;
public static final int COLOR_EXCURSION = 0xFF4CAF50;
public static final int COLOR_READING = 0xFFFFEB3B;
public static final int COLOR_CLASS_EVENT = 0xff388e3c;
public static final int COLOR_INFORMATION = 0xff039be5;
public static final int COLOR_TEACHER_ABSENCE = 0xff039be5;
@ColumnInfo(name = "eventType")
public long type = TYPE_DEFAULT;
@ColumnInfo(name = "eventAddedManually")
public boolean addedManually;
@ColumnInfo(name = "eventSharedBy")
public String sharedBy = null;
@ColumnInfo(name = "eventSharedByName")
public String sharedByName = null;
@ColumnInfo(name = "eventBlacklisted")
public boolean blacklisted = false;
public long teacherId;
public long subjectId;
public long teamId;
@Ignore
public Event() {}
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, long type, boolean addedManually, long teacherId, long subjectId, long teamId)
{
this.profileId = profileId;
this.id = id;
this.eventDate = eventDate;
this.startTime = startTime;
this.topic = topic;
this.color = color;
this.type = type;
this.addedManually = addedManually;
this.teacherId = teacherId;
this.subjectId = subjectId;
this.teamId = teamId;
}
@Ignore
public EventFull withMetadata(Metadata metadata) {
return new EventFull(this, metadata);
}
@Ignore
public Calendar getStartTimeCalendar() {
Calendar c = Calendar.getInstance();
c.set(
eventDate.year,
eventDate.month - 1,
eventDate.day,
(startTime == null) ? 0 : startTime.hour,
(startTime == null) ? 0 : startTime.minute,
(startTime == null) ? 0 : startTime.second
);
return c;
}
@Ignore
public Calendar getEndTimeCalendar() {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(getStartTimeCalendar().getTimeInMillis() + (45 * 60 * 1000));
return c;
}
@Override
public Event clone() {
Event event = new Event(
profileId,
id,
eventDate.clone(),
startTime == null ? null : startTime.clone(),
topic,
color,
type,
addedManually,
subjectId,
teacherId,
teamId
);
event.sharedBy = sharedBy;
event.sharedByName = sharedByName;
event.blacklisted = blacklisted;
return event;
}
@Override
public String toString() {
return "Event{" +
"profileId=" + profileId +
", id=" + id +
", eventDate=" + eventDate +
", startTime=" + startTime +
", topic='" + topic + '\'' +
", color=" + color +
", type=" + type +
", addedManually=" + addedManually +
", sharedBy='" + sharedBy + '\'' +
", sharedByName='" + sharedByName + '\'' +
", teacherId=" + teacherId +
", subjectId=" + subjectId +
", teamId=" + teamId +
'}';
}
}

View File

@ -0,0 +1,107 @@
/*
* 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
import com.google.gson.annotations.SerializedName
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.*
@Entity(tableName = "events",
primaryKeys = ["profileId", "eventId"],
indices = [
Index(value = ["profileId", "eventDate", "eventTime"]),
Index(value = ["profileId", "eventType"])
])
open class Event(
/* This needs to be mutable: see SzkolnyApi.getEvents() */
var profileId: Int,
@ColumnInfo(name = "eventId")
var id: Long,
@ColumnInfo(name = "eventDate")
@SerializedName("eventDate")
var date: Date,
@ColumnInfo(name = "eventTime")
@SerializedName("startTime")
var time: Time?,
@ColumnInfo(name = "eventTopic")
var topic: String,
@ColumnInfo(name = "eventColor")
var color: Int?,
@ColumnInfo(name = "eventType")
var type: Long,
var teacherId: Long,
var subjectId: Long,
var teamId: Long
) {
companion object {
const val TYPE_UNDEFINED = -2L
const val TYPE_HOMEWORK = -1L
const val TYPE_DEFAULT = 0L
const val TYPE_EXAM = 1L
const val TYPE_SHORT_QUIZ = 2L
const val TYPE_ESSAY = 3L
const val TYPE_PROJECT = 4L
const val TYPE_PT_MEETING = 5L
const val TYPE_EXCURSION = 6L
const val TYPE_READING = 7L
const val TYPE_CLASS_EVENT = 8L
const val TYPE_INFORMATION = 9L
const val TYPE_TEACHER_ABSENCE = 10L
const val COLOR_HOMEWORK = 0xff795548.toInt()
const val COLOR_DEFAULT = 0xffffc107.toInt()
const val COLOR_EXAM = 0xfff44336.toInt()
const val COLOR_SHORT_QUIZ = 0xff76ff03.toInt()
const val COLOR_ESSAY = 0xFF4050B5.toInt()
const val COLOR_PROJECT = 0xFF673AB7.toInt()
const val COLOR_PT_MEETING = 0xff90caf9.toInt()
const val COLOR_EXCURSION = 0xFF4CAF50.toInt()
const val COLOR_READING = 0xFFFFEB3B.toInt()
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
const val COLOR_INFORMATION = 0xff039be5.toInt()
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
}
@ColumnInfo(name = "eventAddedManually")
var addedManually: Boolean = false
@ColumnInfo(name = "eventSharedBy")
var sharedBy: String? = null
@ColumnInfo(name = "eventSharedByName")
var sharedByName: String? = null
@ColumnInfo(name = "eventBlacklisted")
var blacklisted: Boolean = false
var homeworkBody: String? = null
var attachmentIds: List<Long>? = null
var attachmentNames: List<String>? = null
@Ignore
var showAsUnseen = false
val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set(
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
val endTimeCalendar: Calendar
get() = startTimeCalendar.also {
it.timeInMillis += 45 * MINUTE * 1000
}
@Ignore
fun withMetadata(metadata: Metadata) = EventFull(this, metadata)
}

View File

@ -52,6 +52,7 @@ data class Notification(
const val TYPE_NEW_ANNOUNCEMENT = 15 const val TYPE_NEW_ANNOUNCEMENT = 15
const val TYPE_FEEDBACK_MESSAGE = 16 const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17 const val TYPE_AUTO_ARCHIVING = 17
const val TYPE_TEACHER_ABSENCE = 19
fun buildId(profileId: Int, type: Int, itemId: Long): Long { fun buildId(profileId: Int, type: Int, itemId: Long): Long {
return 1000000000000 + profileId*10000000000 + type*100000000 + itemId; return 1000000000000 + profileId*10000000000 + type*100000000 + itemId;

View File

@ -1,117 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full;
import pl.szczodrzynski.edziennik.data.db.entity.Event;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
public class EventFull extends Event {
public String typeName = "";
public int typeColor = -1;
public String teacherFullName = "";
public String subjectLongName = "";
public String subjectShortName = "";
public String teamName = "";
public String teamCode = null;
// metadata
public boolean seen;
public boolean notified;
public long addedDate;
public EventFull() {}
public EventFull(Event event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
}
public EventFull(EventFull event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
this.typeName = event.typeName;
this.typeColor = event.typeColor;
this.teacherFullName = event.teacherFullName;
this.subjectLongName = event.subjectLongName;
this.subjectShortName = event.subjectShortName;
this.teamName = event.teamName;
this.teamCode = event.teamCode;
this.seen = event.seen;
this.notified = event.notified;
this.addedDate = event.addedDate;
}
public EventFull(Event event, Metadata metadata) {
this(event);
this.seen = metadata.seen;
this.notified = metadata.notified;
this.addedDate = metadata.addedDate;
}
public int getColor() {
return color == -1 ? typeColor : color;
}
@Override
public String toString() {
return "EventFull{" +
"profileId=" + profileId +
", id=" + id +
", eventDate=" + eventDate +
", startTime=" + startTime +
", topic='" + topic + '\'' +
", color=" + color +
", type=" + type +
", addedManually=" + addedManually +
", sharedBy='" + sharedBy + '\'' +
", sharedByName='" + sharedByName + '\'' +
", blacklisted=" + blacklisted +
", teacherId=" + teacherId +
", subjectId=" + subjectId +
", teamId=" + teamId +
", typeName='" + typeName + '\'' +
", teacherFullName='" + teacherFullName + '\'' +
", subjectLongName='" + subjectLongName + '\'' +
", subjectShortName='" + subjectShortName + '\'' +
", teamName='" + teamName + '\'' +
", seen=" + seen +
", notified=" + notified +
", addedDate=" + addedDate +
'}';
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class EventFull(
profileId: Int, id: Long, date: Date, time: Time?,
topic: String, color: Int?, type: Long,
teacherId: Long, subjectId: Long, teamId: Long
) : Event(
profileId, id, date, time,
topic, color, type,
teacherId, subjectId, teamId
) {
constructor(event: Event, metadata: Metadata? = null) : this(
event.profileId, event.id, event.date, event.time,
event.topic, event.color, event.type,
event.teacherId, event.subjectId, event.teamId) {
event.let {
addedManually = it.addedManually
sharedBy = it.sharedBy
sharedByName = it.sharedByName
blacklisted = it.blacklisted
homeworkBody = it.homeworkBody
attachmentIds = it.attachmentIds
attachmentNames = it.attachmentNames
}
metadata?.let {
seen = it.seen
notified = it.notified
addedDate = it.addedDate
}
}
var typeName: String? = null
var typeColor: Int? = null
var teacherName: String? = null
var subjectLongName: String? = null
var subjectShortName: String? = null
var teamName: String? = null
var teamCode: String? = null
// metadata
var seen = false
var notified = false
var addedDate: Long = 0
val eventColor
get() = color ?: typeColor ?: 0xff2196f3.toInt()
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-27.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration80 : Migration(79, 80) {
override fun migrate(database: SupportSQLiteDatabase) {
// The Homework Update
database.execSQL("ALTER TABLE events RENAME TO _events;")
database.execSQL("""CREATE TABLE events (
profileId INTEGER NOT NULL,
eventId INTEGER NOT NULL,
eventDate TEXT NOT NULL,
eventTime TEXT,
eventTopic TEXT NOT NULL,
eventColor INTEGER,
eventType INTEGER NOT NULL,
teacherId INTEGER NOT NULL,
subjectId INTEGER NOT NULL,
teamId INTEGER NOT NULL,
eventAddedManually INTEGER NOT NULL DEFAULT 0,
eventSharedBy TEXT DEFAULT NULL,
eventSharedByName TEXT DEFAULT NULL,
eventBlacklisted INTEGER NOT NULL DEFAULT 0,
homeworkBody TEXT DEFAULT NULL,
attachmentIds TEXT DEFAULT NULL,
attachmentNames TEXT DEFAULT NULL,
PRIMARY KEY(profileId, eventId)
)""")
database.execSQL("DROP INDEX IF EXISTS index_events_profileId_eventDate_eventStartTime")
database.execSQL("DROP INDEX IF EXISTS index_events_profileId_eventType")
database.execSQL("CREATE INDEX index_events_profileId_eventDate_eventTime ON events (profileId, eventDate, eventTime)")
database.execSQL("CREATE INDEX index_events_profileId_eventType ON events (profileId, eventType)")
database.execSQL("""
INSERT INTO events (profileId, eventId, eventDate, eventTime, eventTopic, eventColor, eventType, teacherId, subjectId, teamId, eventAddedManually, eventSharedBy, eventSharedByName, eventBlacklisted)
SELECT profileId, eventId, eventDate, eventStartTime, eventTopic,
CASE eventColor WHEN -1 THEN NULL ELSE eventColor END,
eventType, teacherId, subjectId, teamId,
eventAddedManually, eventSharedBy, eventSharedByName, eventBlacklisted
FROM _events
""")
database.execSQL("DROP TABLE _events")
}
}

View File

@ -0,0 +1,11 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
class Migration81 : Migration(80, 81) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE metadata SET seen = 1, notified = 1 WHERE thingType = ${Metadata.TYPE_TEACHER_ABSENCE}")
}
}

View File

@ -9,8 +9,8 @@ import android.app.PendingIntent
import android.app.PendingIntent.CanceledException import android.app.PendingIntent.CanceledException
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import com.google.firebase.iid.zzaq import com.google.firebase.iid.zzad
import com.google.firebase.iid.zzv import com.google.firebase.iid.zzaz
import com.google.firebase.messaging.zzc import com.google.firebase.messaging.zzc
import com.google.gson.JsonObject import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
@ -36,7 +36,7 @@ open class FirebaseService : zzc() {
// apparently this gets the correct intent from some // apparently this gets the correct intent from some
// kind of queue inside Firebase's InstanceID Receiver // kind of queue inside Firebase's InstanceID Receiver
final override fun zza(intent: Intent?) = zzaq.zza()?.zzb() final override fun zza(intent: Intent?) = zzaz.zza()?.zzb()
final override fun zzb(intent: Intent?): Boolean { final override fun zzb(intent: Intent?): Boolean {
val action = intent?.action val action = intent?.action
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") { if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
@ -80,7 +80,7 @@ open class FirebaseService : zzc() {
val ackBundle = Bundle( val ackBundle = Bundle(
"google.message_id" to messageId "google.message_id" to messageId
) )
zzv.zza(this).zza(2, ackBundle) zzad.zza(this).zza(2, ackBundle)
} }
// check for duplicate message // check for duplicate message
// and add it to queue // and add it to queue

View File

@ -36,7 +36,7 @@ class MyFirebaseService : FirebaseService(), CoroutineScope {
putString(System.currentTimeMillis().toString(), message.toString()) putString(System.currentTimeMillis().toString(), message.toString())
apply() apply()
} }
val profiles = app.db.profileDao().profilesForSyncNow val profiles = app.db.profileDao().profilesForFirebaseNow
when (message.from) { when (message.from) {
"640759989760" -> SzkolnyAppFirebase(app, profiles, message) "640759989760" -> SzkolnyAppFirebase(app, profiles, message)
"747285019373" -> SzkolnyMobidziennikFirebase(app, profiles, message) "747285019373" -> SzkolnyMobidziennikFirebase(app, profiles, message)

View File

@ -101,27 +101,27 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
val notificationList = mutableListOf<Notification>() val notificationList = mutableListOf<Notification>()
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team -> teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (profile.registration != Profile.REGISTRATION_ENABLED)
return@forEach
val event = Event( val event = Event(
team.profileId, profileId = team.profileId,
json.getLong("id") ?: return, id = json.getLong("id") ?: return,
json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return, date = json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
json.getInt("startTime")?.let { Time.fromValue(it) }, time = json.getInt("startTime")?.let { Time.fromValue(it) },
json.getString("topic") ?: "", topic = json.getString("topic") ?: "",
json.getInt("color") ?: -1, color = json.getInt("color"),
json.getLong("type") ?: 0, type = json.getLong("type") ?: 0,
true, teacherId = json.getLong("teacherId") ?: -1,
json.getLong("teacherId") ?: -1, subjectId = json.getLong("subjectId") ?: -1,
json.getLong("subjectId") ?: -1, teamId = team.id
team.id
) )
if (event.color == -1)
// TODO? i guess - this comment is here for like a year event.color = null
//val oldEvent: Event? = app.db.eventDao().getByIdNow(profile?.id ?: -1, event.id)
event.sharedBy = json.getString("sharedBy") event.sharedBy = json.getString("sharedBy")
event.sharedByName = json.getString("sharedByName") event.sharedByName = json.getString("sharedByName")
if (profile?.userCode == event.sharedBy) event.sharedBy = "self" if (profile.userCode == event.sharedBy) event.sharedBy = "self"
val metadata = Metadata( val metadata = Metadata(
event.profileId, event.profileId,
@ -132,18 +132,6 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
json.getLong("addedDate") ?: System.currentTimeMillis() json.getLong("addedDate") ?: System.currentTimeMillis()
) )
//val eventType = eventTypes.firstOrNull { it.profileId == profile?.id && it.id == event.type }
/*val text = app.getString(
if (oldEvent == null)
R.string.notification_shared_event_format
else
R.string.notification_shared_event_modified_format,
event.sharedByName,
eventType?.name ?: "wydarzenie",
event.eventDate.formattedString,
event.topic
)*/
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
@ -153,11 +141,11 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = message, text = message,
type = type, type = type,
profileId = profile?.id, profileId = profile.id,
profileName = profile?.name, profileName = profile.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate addedDate = metadata.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
notificationList += notification notificationList += notification
} }
@ -177,18 +165,20 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
val notificationList = mutableListOf<Notification>() val notificationList = mutableListOf<Notification>()
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team -> teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (profile.registration != Profile.REGISTRATION_ENABLED)
return@forEach
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) { if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) {
val notification = Notification( val notification = Notification(
id = Notification.buildId(profile?.id id = Notification.buildId(profile.id
?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId), ?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT), title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
text = message, text = message,
type = Notification.TYPE_REMOVED_SHARED_EVENT, type = Notification.TYPE_REMOVED_SHARED_EVENT,
profileId = profile?.id, profileId = profile.id,
profileName = profile?.name, profileName = profile.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA viewId = MainActivity.DRAWER_ITEM_AGENDA
) )
notificationList += notification notificationList += notification

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
*/
package pl.szczodrzynski.edziennik.network.cookie
import okhttp3.Cookie
class DumbCookie(var cookie: Cookie) {
constructor(domain: String, name: String, value: String, expiresAt: Long? = null) : this(
Cookie.Builder()
.name(name)
.value(value)
.also { if (expiresAt != null) it.expiresAt(expiresAt) }
.domain(domain)
.build()
)
init {
cookie = Cookie.Builder()
.name(cookie.name())
.value(cookie.value())
.expiresAt(cookie.expiresAt())
.domain(cookie.domain())
.build()
}
fun domainMatches(host: String): Boolean {
val domain = cookie.domain()
return host == domain || host.endsWith(".$domain")
}
override fun equals(other: Any?): Boolean {
if (other !is DumbCookie) return false
if (this.cookie === other.cookie) return true
return cookie.name() == other.cookie.name()
&& cookie.domain() == other.cookie.domain()
}
override fun hashCode(): Int {
var hash = 17
hash = 31 * hash + cookie.name().hashCode()
hash = 31 * hash + cookie.domain().hashCode()
return hash
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
*/
package pl.szczodrzynski.edziennik.network.cookie
import android.content.Context
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
/**
* A simple cookie jar that does not care about the [Cookie.secure], [Cookie.hostOnly],
* [Cookie.httpOnly] and [Cookie.path] attributes.
*/
class DumbCookieJar(
/**
* A context to create the shared prefs file.
*/
context: Context,
/**
* Whether to persist session cookies as well, when [Cookie.persistent] is false.
*/
private val persistAll: Boolean = false
) : CookieJar {
private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE)
private val sessionCookies = mutableSetOf<DumbCookie>()
private val savedCookies = mutableSetOf<DumbCookie>()
private fun save(dc: DumbCookie) {
sessionCookies.remove(dc)
sessionCookies.add(dc)
if (dc.cookie.persistent() || persistAll) {
savedCookies.remove(dc)
savedCookies.add(dc)
}
}
private fun delete(vararg toRemove: DumbCookie) {
sessionCookies.removeAll(toRemove)
savedCookies.removeAll(toRemove)
}
override fun saveFromResponse(url: HttpUrl?, cookies: List<Cookie>) {
for (cookie in cookies) {
val dc = DumbCookie(cookie)
save(dc)
}
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return sessionCookies.filter {
it.cookie.matches(url)
}.map { it.cookie }
}
fun get(domain: String, name: String): String? {
return sessionCookies.firstOrNull {
it.domainMatches(domain) && it.cookie.name() == name
}?.cookie?.value()
}
fun set(domain: String, name: String, value: String?, isSession: Boolean) = set(
domain, name, value,
if (isSession) null
else System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L
)
/**
* Add a cookie to the cache.
* By default a session cookie is added. If [expiresAt] is set, the cookie is
* additionally persisted.
*/
fun set(domain: String, name: String?, value: String?, expiresAt: Long? = null) {
name ?: return
if (value == null) {
remove(domain, name)
return
}
val dc = DumbCookie(domain, name, value, expiresAt)
save(dc)
}
fun getAll(domain: String): Map<String, String> {
return sessionCookies.filter {
it.domainMatches(domain)
}.map { it.cookie.name() to it.cookie.value() }.toMap()
}
fun remove(domain: String, name: String) {
val toRemove = sessionCookies.filter {
it.domainMatches(domain) && it.cookie.name() == name
}
delete(*toRemove.toTypedArray())
}
fun clear(domain: String) {
val toRemove = sessionCookies.filter {
it.domainMatches(domain)
}
delete(*toRemove.toTypedArray())
}
}

View File

@ -40,7 +40,7 @@ class QrScannerDialog(
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
app = activity.applicationContext as App app = activity.applicationContext as App
scannerView = ZXingScannerView(activity) scannerView = ZXingScannerView(activity)
scannerView.setPadding(0, 16.dp, 0, 0) scannerView.setPadding(0, 16.dp, 2.dp, 0)
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.qr_scanner_dialog_title) .setTitle(R.string.qr_scanner_dialog_title)
.setView(scannerView) .setView(scannerView)
@ -59,4 +59,4 @@ class QrScannerDialog(
} }
scannerView.startCamera() scannerView.startCamera()
}} }}
} }

View File

@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -47,6 +48,8 @@ class EventDetailsDialog(
SzkolnyApi(app) SzkolnyApi(app)
} }
private var progressDialog: AlertDialog? = null
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
return@run return@run
@ -64,6 +67,7 @@ class EventDetailsDialog(
} }
.setOnDismissListener { .setOnDismissListener {
onDismissListener?.invoke(TAG) onDismissListener?.invoke(TAG)
progressDialog?.dismiss()
} }
.show() .show()
@ -83,11 +87,11 @@ class EventDetailsDialog(
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
try { try {
b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.eventDate.month - 1] b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.date.month - 1]
} }
catch (_: Exception) {} catch (_: Exception) {}
b.typeColor.background?.setTintColor(event.getColor()) b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf( b.details = mutableListOf(
event.subjectLongName, event.subjectLongName,
@ -98,14 +102,14 @@ class EventDetailsDialog(
when (event.sharedBy) { when (event.sharedBy) {
null -> when { null -> when {
event.addedManually -> R.string.event_details_added_by_self_format event.addedManually -> R.string.event_details_added_by_self_format
event.teacherFullName == null -> R.string.event_details_added_by_unknown_format event.teacherName == null -> R.string.event_details_added_by_unknown_format
else -> R.string.event_details_added_by_format else -> R.string.event_details_added_by_format
} }
"self" -> R.string.event_details_shared_by_self_format "self" -> R.string.event_details_shared_by_self_format
else -> R.string.event_details_shared_by_format else -> R.string.event_details_shared_by_format
}, },
Date.fromMillis(event.addedDate).formattedString, Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: "" event.sharedByName ?: event.teacherName ?: ""
) )
b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE
@ -121,7 +125,7 @@ class EventDetailsDialog(
b.goToTimetableButton.setOnClickListener { b.goToTimetableButton.setOnClickListener {
dialog.dismiss() dialog.dismiss()
val dateStr = event.eventDate?.stringY_m_d ?: return@setOnClickListener val dateStr = event.date.stringY_m_d
val intent = val intent =
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE) if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE)
@ -156,6 +160,23 @@ class EventDetailsDialog(
Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true true
} }
b.topic.text = event.topic
BetterLink.attach(b.topic) {
dialog.dismiss()
}
}
private fun showRemovingProgressDialog() {
if (progressDialog != null) {
return
}
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.event_removing_text)
.setCancelable(false)
.show()
} }
private fun showRemoveEventDialog() { private fun showRemoveEventDialog() {
@ -186,11 +207,14 @@ class EventDetailsDialog(
launch { launch {
if (eventShared && eventOwn) { if (eventShared && eventOwn) {
// unshare + remove own event // unshare + remove own event
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show() showRemovingProgressDialog()
api.runCatching(activity) { api.runCatching(activity) {
unshareEvent(event) unshareEvent(event)
} ?: return@launch } ?: run {
progressDialog?.dismiss()
return@launch
}
finishRemoving() finishRemoving()
} else if (eventShared && !eventOwn) { } else if (eventShared && !eventOwn) {
@ -202,6 +226,7 @@ class EventDetailsDialog(
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
finishRemoving() finishRemoving()
} }
progressDialog?.dismiss()
} }
} }
@ -220,7 +245,7 @@ class EventDetailsDialog(
} }
private fun openInCalendar() { launch { private fun openInCalendar() { launch {
val title = (event.typeName ?: "") + val title = event.typeName ?: "" +
(if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") + (if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") +
(event.subjectLongName ?: "") (event.subjectLongName ?: "")
@ -229,12 +254,12 @@ class EventDetailsDialog(
putExtra(Events.TITLE, title) putExtra(Events.TITLE, title)
putExtra(Events.DESCRIPTION, event.topic) putExtra(Events.DESCRIPTION, event.topic)
if (event.startTime == null) { if (event.time == null) {
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.eventDate.inMillis) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.date.inMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.eventDate.inMillis) putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.date.inMillis)
} else { } else {
val startTime = event.eventDate.combineWith(event.startTime) val startTime = event.date.combineWith(event.time)
val endTime = startTime + 45 * 60 * 1000 /* 45 min */ val endTime = startTime + 45 * 60 * 1000 /* 45 min */
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false)

View File

@ -50,10 +50,10 @@ class EventListAdapter(
b.topic.text = event.topic b.topic.text = event.topic
b.details.text = mutableListOf<CharSequence?>( b.details.text = mutableListOf<CharSequence?>(
if (showWeekDay) Week.getFullDayName(event.eventDate.weekDay) else null, if (showWeekDay) Week.getFullDayName(event.date.weekDay) else null,
if (showDate) event.eventDate.getRelativeString(context, 7) ?: event.eventDate.formattedStringShort else null, if (showDate) event.date.getRelativeString(context, 7) ?: event.date.formattedStringShort else null,
event.typeName, event.typeName,
if (simpleMode) null else event.startTime?.stringHM ?: app.getString(R.string.event_all_day), if (simpleMode) null else event.time?.stringHM ?: app.getString(R.string.event_all_day),
if (simpleMode) null else event.subjectLongName if (simpleMode) null else event.subjectLongName
).concat(bullet) ).concat(bullet)
@ -61,18 +61,18 @@ class EventListAdapter(
when (event.sharedBy) { when (event.sharedBy) {
null -> when { null -> when {
event.addedManually -> R.string.event_list_added_by_self_format event.addedManually -> R.string.event_list_added_by_self_format
event.teacherFullName == null -> R.string.event_list_added_by_unknown_format event.teacherName == null -> R.string.event_list_added_by_unknown_format
else -> R.string.event_list_added_by_format else -> R.string.event_list_added_by_format
} }
"self" -> R.string.event_list_shared_by_self_format "self" -> R.string.event_list_shared_by_self_format
else -> R.string.event_list_shared_by_format else -> R.string.event_list_shared_by_format
}, },
Date.fromMillis(event.addedDate).formattedString, Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: "", event.sharedByName ?: event.teacherName ?: "",
event.teamName?.let { bullet+it } ?: "" event.teamName?.let { bullet+it } ?: ""
) )
b.typeColor.background?.setTintColor(event.getColor()) b.typeColor.background?.setTintColor(event.eventColor)
b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE
b.editButton.onClick { b.editButton.onClick {

View File

@ -29,9 +29,11 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.EventType import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationEnableDialog
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
@ -62,12 +64,12 @@ class EventManualDialog(
private val app by lazy { activity.application as App } private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private var profile: Profile? = null
private var customColor: Int? = null private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null private val editingShared = editingEvent?.sharedBy != null
private val editingOwn = editingEvent?.sharedBy == "self" private val editingOwn = editingEvent?.sharedBy == "self"
private var removeEventDialog: AlertDialog? = null private var removeEventDialog: AlertDialog? = null
private var defaultLoaded = false
private val api by lazy { private val api by lazy {
SzkolnyApi(app) SzkolnyApi(app)
@ -76,6 +78,8 @@ class EventManualDialog(
private var enqueuedWeekDialog: AlertDialog? = null private var enqueuedWeekDialog: AlertDialog? = null
private var enqueuedWeekStart = Date.getToday() private var enqueuedWeekStart = Date.getToday()
private var progressDialog: AlertDialog? = null
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
return@run return@run
@ -95,6 +99,8 @@ class EventManualDialog(
.setOnDismissListener { .setOnDismissListener {
onDismissListener?.invoke(TAG) onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@EventManualDialog) EventBus.getDefault().unregister(this@EventManualDialog)
enqueuedWeekDialog?.dismiss()
progressDialog?.dismiss()
} }
.setCancelable(false) .setCancelable(false)
.create() .create()
@ -168,7 +174,7 @@ class EventManualDialog(
enqueuedWeekStart = weekStart enqueuedWeekStart = weekStart
EdziennikTask.syncProfile( EdziennikTask.syncProfile(
profileId = App.profileId, profileId = profileId,
viewIds = listOf( viewIds = listOf(
MainActivity.DRAWER_ITEM_TIMETABLE to 0 MainActivity.DRAWER_ITEM_TIMETABLE to 0
), ),
@ -178,13 +184,40 @@ class EventManualDialog(
).enqueue(activity) ).enqueue(activity)
} }
private fun showSharingProgressDialog() {
if (progressDialog != null) {
return
}
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.event_sharing_text)
.setCancelable(false)
.show()
}
private fun showRemovingProgressDialog() {
if (progressDialog != null) {
return
}
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.event_removing_text)
.setCancelable(false)
.show()
}
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
if (event.profileId == App.profileId) { if (event.profileId == profileId) {
enqueuedWeekDialog?.dismiss() enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null enqueuedWeekDialog = null
progressDialog?.dismiss()
launch { launch {
b.timeDropdown.loadItems() b.timeDropdown.loadItems()
b.timeDropdown.selectDefault(editingEvent?.time)
b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
} }
} }
} }
@ -193,19 +226,22 @@ class EventManualDialog(
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
enqueuedWeekDialog?.dismiss() enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null enqueuedWeekDialog = null
progressDialog?.dismiss()
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
dialog.dismiss()
enqueuedWeekDialog?.dismiss() enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null enqueuedWeekDialog = null
progressDialog?.dismiss()
} }
private fun loadLists() { launch { private fun loadLists() { launch {
profile = withContext(Dispatchers.Default) { app.db.profileDao().getByIdNow(profileId) }
with (b.dateDropdown) { with (b.dateDropdown) {
db = app.db db = app.db
profileId = App.profileId profileId = this@EventManualDialog.profileId
showWeekDays = false showWeekDays = false
showDays = true showDays = true
showOtherDate = true showOtherDate = true
@ -215,7 +251,7 @@ class EventManualDialog(
nextLessonTeamId = it.displayTeamId nextLessonTeamId = it.displayTeamId
} }
loadItems() loadItems()
selectDefault(editingEvent?.eventDate) selectDefault(editingEvent?.date)
selectDefault(defaultLesson?.displayDate ?: defaultDate) selectDefault(defaultLesson?.displayDate ?: defaultDate)
onDateSelected = { date, lesson -> onDateSelected = { date, lesson ->
b.timeDropdown.deselect() b.timeDropdown.deselect()
@ -233,14 +269,16 @@ class EventManualDialog(
with (b.timeDropdown) { with (b.timeDropdown) {
db = app.db db = app.db
profileId = App.profileId profileId = this@EventManualDialog.profileId
showAllDay = true showAllDay = true
showCustomTime = true showCustomTime = true
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday() lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
displayMode = DISPLAY_LESSONS displayMode = DISPLAY_LESSONS
if (!loadItems()) if (!loadItems())
syncTimetable(lessonsDate ?: Date.getToday()) syncTimetable(lessonsDate ?: Date.getToday())
selectDefault(editingEvent?.startTime) selectDefault(editingEvent?.time)
if (editingEvent != null && editingEvent.time == null)
select(0L)
selectDefault(defaultLesson?.displayStartTime ?: defaultTime) selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
onLessonSelected = { lesson -> onLessonSelected = { lesson ->
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect() lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
@ -251,7 +289,7 @@ class EventManualDialog(
with (b.teamDropdown) { with (b.teamDropdown) {
db = app.db db = app.db
profileId = App.profileId profileId = this@EventManualDialog.profileId
showNoTeam = true showNoTeam = true
loadItems() loadItems()
selectTeamClass() selectTeamClass()
@ -261,7 +299,7 @@ class EventManualDialog(
with (b.subjectDropdown) { with (b.subjectDropdown) {
db = app.db db = app.db
profileId = App.profileId profileId = this@EventManualDialog.profileId
showNoSubject = true showNoSubject = true
showCustomSubject = false showCustomSubject = false
loadItems() loadItems()
@ -271,7 +309,7 @@ class EventManualDialog(
with (b.teacherDropdown) { with (b.teacherDropdown) {
db = app.db db = app.db
profileId = App.profileId profileId = this@EventManualDialog.profileId
showNoTeacher = true showNoTeacher = true
loadItems() loadItems()
selectDefault(editingEvent?.teacherId) selectDefault(editingEvent?.teacherId)
@ -281,7 +319,12 @@ class EventManualDialog(
val deferred = async(Dispatchers.Default) { val deferred = async(Dispatchers.Default) {
// get the event type list // get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId) var eventTypes = app.db.eventTypeDao().getAllNow(profileId)
if (eventTypes.none { it.id in -1L..10L }) {
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
}
b.typeDropdown.clear() b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) } b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
} }
@ -300,10 +343,10 @@ class EventManualDialog(
// copy IDs from event being edited // copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.topic.setText(it.topic) b.topic.setText(it.topic)
b.typeDropdown.select(it.type.toLong())?.let { item -> b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color customColor = (item.tag as EventType).color
} }
if (it.color != -1) if (it.color != null && it.color != -1)
customColor = it.color customColor = it.color
} }
@ -323,7 +366,7 @@ class EventManualDialog(
} }
b.typeColor.onClick { b.typeColor.onClick {
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder() val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor) .setColor(currentColor)
.create() .create()
@ -365,16 +408,24 @@ class EventManualDialog(
private fun saveEvent() { private fun saveEvent() {
val date = b.dateDropdown.getSelected() as? Date val date = b.dateDropdown.getSelected() as? Date
val startTimePair = b.timeDropdown.getSelected() as? Pair<*, *> val timeSelected = b.timeDropdown.getSelected()
val startTime = startTimePair?.first as? Time
val teamId = b.teamDropdown.getSelected() as? Long val teamId = b.teamDropdown.getSelected() as? Long
val type = b.typeDropdown.selected?.id val type = b.typeDropdown.selected?.id
val topic = b.topic.text?.toString() val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.getSelected() as? Long val subjectId = b.subjectDropdown.getSelected() as? Long
val teacherId = b.teacherDropdown.getSelected() as? Long val teacherId = b.teacherDropdown.getSelected()
val share = b.shareSwitch.isChecked val share = b.shareSwitch.isChecked
if (share && profile?.registration != Profile.REGISTRATION_ENABLED) {
RegistrationEnableDialog(activity, profileId).showEventShareDialog {
if (it != null)
profile = it
saveEvent()
}
return
}
b.dateDropdown.error = null b.dateDropdown.error = null
b.teamDropdown.error = null b.teamDropdown.error = null
b.typeDropdown.error = null b.typeDropdown.error = null
@ -384,41 +435,59 @@ class EventManualDialog(
if (date == null) { if (date == null) {
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose) b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
b.dateDropdown.requestFocus()
isError = true
}
if (timeSelected !is Pair<*, *> && timeSelected != 0L) {
b.timeDropdown.error = app.getString(R.string.dialog_event_manual_time_choose)
if (!isError) b.timeDropdown.parent.requestChildFocus(b.timeDropdown, b.timeDropdown)
isError = true isError = true
} }
if (share && teamId == null) { if (share && teamId == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
isError = true isError = true
} }
if (type == null) { if (type == null) {
b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose) b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose)
if (!isError) b.typeDropdown.requestFocus()
isError = true isError = true
} }
if (topic.isNullOrBlank()) { if (topic.isNullOrBlank()) {
b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose) b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose)
if (!isError) b.topic.requestFocus()
isError = true isError = true
} }
val startTime = if (timeSelected == 0L)
null
else
(timeSelected as? Pair<*, *>)?.first as? Time
if (isError) return if (isError) return
date ?: return
topic ?: return
val id = System.currentTimeMillis() val id = System.currentTimeMillis()
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
editingEvent?.id ?: id, id = editingEvent?.id ?: id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
customColor ?: -1, color = customColor,
type ?: Event.TYPE_DEFAULT, type = type ?: Event.TYPE_DEFAULT,
true, teacherId = teacherId ?: -1,
teacherId ?: -1, subjectId = subjectId ?: -1,
subjectId ?: -1, teamId = teamId ?: -1
teamId ?: -1 ).also {
) it.addedManually = true
}
val metadataObject = Metadata( val metadataObject = Metadata(
profileId, profileId,
@ -436,7 +505,7 @@ class EventManualDialog(
val profile = app.db.profileDao().getByIdNow(profileId) val profile = app.db.profileDao().getByIdNow(profileId)
if (!share && !editingShared) { if (!share && !editingShared) {
Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show() //Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show()
finishAdding(eventObject, metadataObject) finishAdding(eventObject, metadataObject)
} }
else if (editingShared && !editingOwn) { else if (editingShared && !editingOwn) {
@ -444,7 +513,7 @@ class EventManualDialog(
// TODO // TODO
} }
else if (!share && editingShared) { else if (!share && editingShared) {
Toast.makeText(activity, R.string.event_manual_unshare, Toast.LENGTH_SHORT).show() showSharingProgressDialog()
eventObject.apply { eventObject.apply {
sharedBy = null sharedBy = null
@ -453,13 +522,16 @@ class EventManualDialog(
api.runCatching(activity) { api.runCatching(activity) {
unshareEvent(eventObject) unshareEvent(eventObject)
} ?: return@launch } ?: run {
progressDialog?.dismiss()
return@launch
}
eventObject.sharedByName = null eventObject.sharedByName = null
finishAdding(eventObject, metadataObject) finishAdding(eventObject, metadataObject)
} }
else if (share) { else if (share) {
Toast.makeText(activity, R.string.event_manual_share, Toast.LENGTH_SHORT).show() showSharingProgressDialog()
eventObject.apply { eventObject.apply {
sharedBy = profile?.userCode sharedBy = profile?.userCode
@ -470,7 +542,10 @@ class EventManualDialog(
api.runCatching(activity) { api.runCatching(activity) {
shareEvent(eventObject.withMetadata(metadataObject)) shareEvent(eventObject.withMetadata(metadataObject))
} ?: return@launch } ?: run {
progressDialog?.dismiss()
return@launch
}
eventObject.sharedBy = "self" eventObject.sharedBy = "self"
finishAdding(eventObject, metadataObject) finishAdding(eventObject, metadataObject)
@ -478,6 +553,7 @@ class EventManualDialog(
else { else {
Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show() Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show()
} }
progressDialog?.dismiss()
} }
} }
@ -485,11 +561,14 @@ class EventManualDialog(
launch { launch {
if (editingShared && editingOwn) { if (editingShared && editingOwn) {
// unshare + remove own event // unshare + remove own event
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show() showRemovingProgressDialog()
api.runCatching(activity) { api.runCatching(activity) {
unshareEvent(editingEvent!!) unshareEvent(editingEvent!!)
} ?: return@launch } ?: run {
progressDialog?.dismiss()
return@launch
}
finishRemoving() finishRemoving()
} else if (editingShared && !editingOwn) { } else if (editingShared && !editingOwn) {
@ -498,9 +577,10 @@ class EventManualDialog(
// TODO // TODO
} else { } else {
// remove event // remove event
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() //Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
finishRemoving() finishRemoving()
} }
progressDialog?.dismiss()
} }
} }
@ -518,6 +598,7 @@ class EventManualDialog(
activity.reloadTarget() activity.reloadTarget()
} }
private fun finishRemoving() { private fun finishRemoving() {
editingEvent ?: return
launch { launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
app.db.eventDao().remove(editingEvent) app.db.eventDao().remove(editingEvent)

View File

@ -58,7 +58,7 @@ class GradeDetailsDialog(
b.weightText = manager.getWeightString(app, grade) b.weightText = manager.getWeightString(app, grade)
b.commentVisible = false b.commentVisible = false
b.devMode = App.debugMode b.devMode = App.debugMode
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0x99000000.toInt() else 0x99ffffff.toInt()) b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt())
b.gradeName.background.setTintColor(gradeColor) b.gradeName.background.setTintColor(gradeColor)
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade) b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)

View File

@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_
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_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
import java.util.*
class GradesConfigDialog( class GradesConfigDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
@ -88,14 +89,34 @@ class GradesConfigDialog(
else -> null else -> null
}?.isChecked = true }?.isChecked = true
b.dontCountZeroToAverage.isChecked = !profileConfig.countZeroToAvg b.dontCountGrades.isChecked = profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty()
b.hideImproved.isChecked = profileConfig.hideImproved b.hideImproved.isChecked = profileConfig.hideImproved
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
if (profileConfig.dontCountGrades.isEmpty()) {
b.dontCountGradesText.setText("nb, 0, bz, bd")
}
else {
b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", "))
}
} }
private fun saveConfig() { private fun saveConfig() {
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
b.dontCountGradesText.setText(
b.dontCountGradesText
.text
?.toString()
?.toLowerCase(Locale.getDefault())
?.replace(", ", ",")
)
profileConfig.dontCountEnabled = b.dontCountGrades.isChecked
profileConfig.dontCountGrades = b.dontCountGradesText.text
?.split(",")
?.map { it.trim() }
?: listOf()
} }
private fun initView() { private fun initView() {
@ -127,7 +148,6 @@ class GradesConfigDialog(
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM } b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_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.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked } b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }

View File

@ -37,7 +37,8 @@ class NotificationFilterDialog(
Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement, 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_EVENT to R.string.notification_type_new_shared_event,
Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework, 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 Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event,
Notification.TYPE_TEACHER_ABSENCE to R.string.notification_type_new_teacher_absence
) )
} }

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-15.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import android.text.Html
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
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.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.task.AppSync
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import kotlin.coroutines.CoroutineContext
class RegistrationEnableDialog(
val activity: AppCompatActivity,
val profileId: Int
) : CoroutineScope {
companion object {
private const val TAG = "RegistrationEnableDialog"
}
private lateinit var app: App
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
private var progressDialog: AlertDialog? = null
init { run {
if (activity.isFinishing)
return@run
app = activity.applicationContext as App
}}
fun showEventShareDialog(onSuccess: (profile: Profile?) -> Unit) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.event_manual_need_registration_title)
.setMessage(R.string.event_manual_need_registration_text)
.setPositiveButton(R.string.ok) { dialog, which ->
enableRegistration(onSuccess)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
fun showEnableDialog(onSuccess: (profile: Profile?) -> Unit) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.registration_enable_dialog_title)
.setMessage(Html.fromHtml(app.getString(R.string.registration_enable_dialog_text)))
.setPositiveButton(R.string.ok) { dialog, which ->
enableRegistration(onSuccess)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
private fun enableRegistration(onSuccess: (profile: Profile?) -> Unit) { launch {
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.registration_enable_progress_text)
.setCancelable(false)
.show()
val profile = withContext(Dispatchers.Default) {
val profile = app.db.profileDao().getByIdNow(profileId) ?: return@withContext null
profile.registration = Profile.REGISTRATION_ENABLED
// force full registration of the user
App.config.getFor(profile.id).hash = ""
AppSync(app, mutableListOf(), listOf(profile), SzkolnyApi(app)).run(0L, markAsSeen = true)
app.db.profileDao().add(profile)
if (profile.id == App.profileId) {
App.profile.registration = profile.registration
}
return@withContext profile
}
progressDialog?.dismiss()
onSuccess(profile)
}}
}

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