Compare commits

..

7 Commits

Author SHA1 Message Date
99afa77a63 [4.11.7] Update build.gradle, signing and changelog. 2022-09-17 16:00:38 +02:00
a5d0f4212d [UI/Login] Make Lab accessible from login page. 2022-09-17 15:00:34 +02:00
a85f935eb4 [UI/Home] Improve displaying cancelled lessons in timetable card. 2022-09-17 14:58:54 +02:00
bb44fa066c [API/Vulcan] Migrate to new MessageBox API. (#134)
* Implement fetching vulcan messages from new api

* Bump kotlin version and fix timetable card lessons size display

* Revert formatting changes

* Revert disabling message composing

* Revert MultiDex changes and dependency upgrades

* Add missing MultiDex dependency, update Google Services

* Separate MessageBoxes sync, revert API data behavior changes

* Revert migrating MessageRecipient to Kotlin

* Use loginId from MessageBox address book

* Revert using compatible HTML mode in Vulcan

* Implement message meta and read status changing

* Always set attachment lists

* Fix setting tutor role description

* Implement sending messages

* Replace millis constant with WEEK

* Revert timetable changes

* Remove unused DataVulcan properties

* Ensure UUID-format recipient IDs

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2022-09-17 12:31:35 +02:00
54a61c6254 [Gradle] Update dependencies, update target SDK to 33 2022-09-09 12:47:20 +02:00
ac10874bf1 [4.11.6] Update build.gradle, signing and changelog. 2022-05-27 21:09:40 +02:00
fa55b4901a [Messages/Librus] Disable text styling. (#130) 2022-05-27 18:55:24 +02:00
36 changed files with 459 additions and 165 deletions

View File

@ -82,9 +82,6 @@ android {
defaultConfig { defaultConfig {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
lintOptions {
checkReleaseBuilds = false
}
buildFeatures { buildFeatures {
dataBinding = true dataBinding = true
viewBinding = true viewBinding = true
@ -98,7 +95,9 @@ android {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
packagingOptions { packagingOptions {
exclude 'META-INF/library-core_release.kotlin_module' resources {
excludes += ['META-INF/library-core_release.kotlin_module']
}
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@ -106,6 +105,9 @@ android {
version "3.10.2" version "3.10.2"
} }
} }
lint {
checkReleaseBuilds false
}
} }
tasks.whenTaskAdded { task -> tasks.whenTaskAdded { task ->
@ -140,28 +142,29 @@ dependencies {
// Language cores // Language cores
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.multidex:multidex:2.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
// Android Jetpack // Android Jetpack
implementation "androidx.appcompat:appcompat:1.3.1" implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5" implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.room:room-runtime:2.3.0" implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.work:work-runtime-ktx:2.6.0" implementation "androidx.work:work-runtime-ktx:2.7.1"
kapt "androidx.room:room-compiler:2.3.0" kapt "androidx.room:room-compiler:2.4.3"
// Google design libs // Google design libs
implementation "com.google.android.material:material:1.4.0" implementation "com.google.android.material:material:1.6.1"
implementation "com.google.android.flexbox:flexbox:3.0.0" implementation "com.google.android.flexbox:flexbox:3.0.0"
// Play Services/Firebase // Play Services/Firebase
implementation "com.google.android.gms:play-services-wearable:17.1.0" implementation "com.google.android.gms:play-services-wearable:17.1.0"
implementation "com.google.firebase:firebase-core:19.0.2" implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } }
implementation "com.google.firebase:firebase-crashlytics:18.2.3" implementation "com.google.firebase:firebase-crashlytics:18.2.13"
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } } implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
// OkHttp, Retrofit, Gson, Jsoup // OkHttp, Retrofit, Gson, Jsoup

View File

@ -43,6 +43,7 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:exported="true"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -66,6 +67,7 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:exported="true"
android:theme="@style/AppTheme.Dark.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" />
@ -73,7 +75,8 @@
</activity> </activity>
<!-- TIMETABLE --> <!-- TIMETABLE -->
<receiver android:name=".ui.widgets.timetable.WidgetTimetableProvider" <receiver android:name=".ui.widgets.timetable.WidgetTimetableProvider"
android:label="@string/widget_timetable_title"> android:label="@string/widget_timetable_title"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
@ -88,10 +91,12 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:exported="true"
android:theme="@style/AppTheme.Dark.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"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
@ -104,7 +109,8 @@
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<!-- LUCKY NUMBER --> <!-- LUCKY NUMBER -->
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider" <receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
android:label="@string/widget_lucky_number_title"> android:label="@string/widget_lucky_number_title"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
@ -124,29 +130,36 @@
<activity android:name=".ui.base.CrashActivity" <activity android:name=".ui.base.CrashActivity"
android:configChanges="orientation|screenSize|keyboardHidden" android:configChanges="orientation|screenSize|keyboardHidden"
android:process=":error_activity" android:process=":error_activity"
android:exported="false"
android:theme="@style/DeadTheme" /> android:theme="@style/DeadTheme" />
<activity android:name=".ui.intro.ChangelogIntroActivity" <activity android:name=".ui.intro.ChangelogIntroActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:exported="false"
android:theme="@style/Theme.Intro" /> android:theme="@style/Theme.Intro" />
<activity android:name=".ui.login.LoginActivity" <activity android:name=".ui.login.LoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:launchMode="singleTop" android:launchMode="singleTop"
android:exported="false"
android:theme="@style/AppTheme.Light" /> android:theme="@style/AppTheme.Light" />
<activity android:name=".ui.home.CounterActivity" <activity android:name=".ui.home.CounterActivity"
android:exported="false"
android:theme="@style/AppTheme.Black" /> android:theme="@style/AppTheme.Black" />
<activity android:name=".ui.feedback.FeedbackActivity" <activity android:name=".ui.feedback.FeedbackActivity"
android:configChanges="orientation|screenSize|keyboardHidden" android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:exported="false"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity android:name=".ui.settings.SettingsLicenseActivity" <activity android:name=".ui.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity android:name="com.canhub.cropper.CropImageActivity" <activity android:name="com.canhub.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@style/Base.Theme.AppCompat" /> android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.base.BuildInvalidActivity" /> <activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
<activity android:name=".ui.settings.contributors.ContributorsActivity" /> <activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
<!-- _____ _ <!-- _____ _
| __ \ (_) | __ \ (_)
@ -156,12 +169,14 @@
|_| \_\___|\___\___|_| \_/ \___|_| |___/ |_| \_\___|\___\___|_| \_/ \___|_| |___/
--> -->
<receiver android:name=".receivers.UserPresentReceiver" <receiver android:name=".receivers.UserPresentReceiver"
android:enabled="true"> android:enabled="true"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.USER_PRESENT" /> <action android:name="android.intent.action.USER_PRESENT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".sync.UpdateDownloaderService$DownloadProgressReceiver"> <receiver android:name=".sync.UpdateDownloaderService$DownloadProgressReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter> </intent-filter>

View File

@ -1,6 +1,8 @@
<h3>Wersja 4.11.5, 2022-04-18</h3> <h3>Wersja 4.11.7, 2022-09-17</h3>
<ul> <ul>
<li>Poprawiono działanie dotyku na systemie Android 12 i nowszych.</li> <li>Vulcan UONET+: naprawiono działanie systemu wiadomości. @Antoni-Czaplicki</li>
<li>Poprawiono wyświetlanie lekcji odwołanych na stronie głównej.</li>
<li>Dodano dostęp do Laboratorium na ekranie logowania.</li>
</ul> </ul>
<br> <br>
<br> <br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0x5d, 0x6b, 0xa0, 0x4e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0x59, 0x01, 0xcc, 0x81, 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

@ -92,7 +92,7 @@ val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
const val VULCAN_HEBE_USER_AGENT = "Dart/2.10 (dart:io)" const val VULCAN_HEBE_USER_AGENT = "Dart/2.10 (dart:io)"
const val VULCAN_HEBE_APP_NAME = "DzienniczekPlus 2.0" const val VULCAN_HEBE_APP_NAME = "DzienniczekPlus 2.0"
const val VULCAN_HEBE_APP_VERSION = "21.02.09 (G)" const val VULCAN_HEBE_APP_VERSION = "22.09.02 (G)"
private const val VULCAN_API_DEVICE_NAME_PREFIX = "Szkolny.eu " private const val VULCAN_API_DEVICE_NAME_PREFIX = "Szkolny.eu "
private const val VULCAN_API_DEVICE_NAME_SUFFIX = " - nie usuwać" private const val VULCAN_API_DEVICE_NAME_SUFFIX = " - nie usuwać"
val VULCAN_API_DEVICE_NAME by lazy { val VULCAN_API_DEVICE_NAME by lazy {
@ -116,9 +116,11 @@ const val VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY = "api/mobile/grade/summary"
const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework" const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework"
const val VULCAN_HEBE_ENDPOINT_NOTICES = "api/mobile/note" const val VULCAN_HEBE_ENDPOINT_NOTICES = "api/mobile/note"
const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson" const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson"
const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message" const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX = "api/mobile/messagebox"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status" const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK = "api/mobile/messagebox/addressbook"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_SEND = "api/mobile/message" const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES = "api/mobile/messagebox/message"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS = "api/mobile/messagebox/message/status"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_SEND = "api/mobile/messagebox/message"
const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky" const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky"
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}" const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -20,6 +20,10 @@ object Regexes {
"""<br\s?/?>""".toRegex() """<br\s?/?>""".toRegex()
} }
val MESSAGE_META by lazy {
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
}
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy { val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {

View File

@ -222,15 +222,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext } get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value } set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
private var mSenderAddressHash: String? = null private var mMessageBoxKey: String? = null
var senderAddressHash: String? var messageBoxKey: String?
get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash } get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey }
set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value } set(value) { profile?.putStudentData("messageBoxKey", value) ?: return; mMessageBoxKey = value }
private var mSenderAddressName: String? = null private var mMessageBoxName: String? = null
var senderAddressName: String? var messageBoxName: String?
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName } get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName }
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value } set(value) { profile?.putStudentData("messageBoxName", value) ?: return; mMessageBoxName = value }
val apiUrl: String? val apiUrl: String?
get() { get() {

View File

@ -12,6 +12,7 @@ const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
const val ENDPOINT_VULCAN_HEBE_MAIN = 3000 const val ENDPOINT_VULCAN_HEBE_MAIN = 3000
const val ENDPOINT_VULCAN_HEBE_PUSH_CONFIG = 3005 const val ENDPOINT_VULCAN_HEBE_PUSH_CONFIG = 3005
const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010 const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010
const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 = 3011
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020 const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030 const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040 const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
@ -19,10 +20,11 @@ const val ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY = 3050
const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060 const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060
const val ENDPOINT_VULCAN_HEBE_NOTICES = 3070 const val ENDPOINT_VULCAN_HEBE_NOTICES = 3070
const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080 const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100
const val ENDPOINT_VULCAN_HEBE_TEACHERS = 3110 const val ENDPOINT_VULCAN_HEBE_TEACHERS = 3110
const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200 const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
const val ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES = 3500
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3510
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3520
val VulcanFeatures = listOf( val VulcanFeatures = listOf(
// timetable // timetable
@ -85,6 +87,8 @@ val VulcanFeatures = listOf(
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf( Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_VULCAN_HEBE_MAIN to LOGIN_METHOD_VULCAN_HEBE, ENDPOINT_VULCAN_HEBE_MAIN to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LOGIN_METHOD_VULCAN_HEBE, ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_TEACHERS to LOGIN_METHOD_VULCAN_HEBE ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_TEACHERS to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES to LOGIN_METHOD_VULCAN_HEBE,
), listOf(LOGIN_METHOD_VULCAN_HEBE)) ), listOf(LOGIN_METHOD_VULCAN_HEBE))
) )

View File

@ -21,10 +21,12 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
ENDPOINT_VULCAN_HEBE_MAIN, ENDPOINT_VULCAN_HEBE_MAIN,
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG, ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2,
ENDPOINT_VULCAN_HEBE_TIMETABLE, ENDPOINT_VULCAN_HEBE_TIMETABLE,
ENDPOINT_VULCAN_HEBE_EXAMS, ENDPOINT_VULCAN_HEBE_EXAMS,
ENDPOINT_VULCAN_HEBE_HOMEWORK, ENDPOINT_VULCAN_HEBE_HOMEWORK,
ENDPOINT_VULCAN_HEBE_NOTICES, ENDPOINT_VULCAN_HEBE_NOTICES,
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES,
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX, ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT, ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
ENDPOINT_VULCAN_HEBE_TEACHERS, ENDPOINT_VULCAN_HEBE_TEACHERS,
@ -107,6 +109,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_addressbook) data.startProgress(R.string.edziennik_progress_endpoint_addressbook)
VulcanHebeAddressbook(data, lastSync, onSuccess) VulcanHebeAddressbook(data, lastSync, onSuccess)
} }
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 -> {
data.startProgress(R.string.edziennik_progress_endpoint_addressbook)
VulcanHebeAddressbook2(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_TEACHERS -> { ENDPOINT_VULCAN_HEBE_TEACHERS -> {
data.startProgress(R.string.edziennik_progress_endpoint_teachers) data.startProgress(R.string.edziennik_progress_endpoint_teachers)
VulcanHebeTeachers(data, lastSync, onSuccess) VulcanHebeTeachers(data, lastSync, onSuccess)
@ -139,6 +145,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_attendance) data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanHebeAttendance(data, lastSync, onSuccess) VulcanHebeAttendance(data, lastSync, onSuccess)
} }
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages)
VulcanHebeMessageBoxes(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> { ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED) VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED)

View File

@ -14,7 +14,6 @@ import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.hebe.getSignatureHeaders import io.github.wulkanowy.signer.hebe.getSignatureHeaders
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.HebeFilterType import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.HebeFilterType
@ -55,6 +54,15 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return date.getLong("Timestamp") ?: return default return date.getLong("Timestamp") ?: return default
} }
fun buildDateTime(): JsonObject {
return JsonObject(
"Timestamp" to System.currentTimeMillis(),
"Date" to Date.getToday().stringY_m_d,
"DateDisplay" to Date.getToday().stringDmy,
"Time" to Time.getNow().stringHMS,
)
}
fun getDate(json: JsonObject?, key: String): Date? { fun getDate(json: JsonObject?, key: String): Date? {
val date = json.getJsonObject(key) val date = json.getJsonObject(key)
return date.getString("Date")?.let { Date.fromY_m_d(it) } return date.getString("Date")?.let { Date.fromY_m_d(it) }
@ -74,6 +82,22 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return teacherId return teacherId
} }
fun getTeacherRecipient(json: JsonObject): Teacher? {
val globalKey = json.getString("GlobalKey") ?: return null
if (globalKey == data.messageBoxKey)
return null
var name = json.getString("Name") ?: return null
val group = json.getString("Group", "P")
val loginId = "${globalKey};${group};${name}"
val pattern = " - $group - (${data.schoolShort})"
if (name.endsWith(pattern))
name = name.substringBefore(pattern)
val teacher = data.getTeacherByFirstLast(name, loginId)
if (teacher.type == 0)
teacher.type = Teacher.TYPE_OTHER
return teacher
}
fun getSubjectId(json: JsonObject?, key: String): Long? { fun getSubjectId(json: JsonObject?, key: String): Long? {
val subject = json.getJsonObject(key) val subject = json.getJsonObject(key)
val subjectId = subject.getLong("Id") ?: return null val subjectId = subject.getLong("Id") ?: return null
@ -89,7 +113,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
} }
fun getTeamId(json: JsonObject?, key: String): Long? { fun getTeamId(json: JsonObject?, key: String): Long? {
val team = json.getJsonObject(key) val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id") val teamId = team.getLong("Id")
var teamName = team.getString("Shortcut") var teamName = team.getString("Shortcut")
?: team.getString("Name") ?: team.getString("Name")
@ -104,7 +128,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
} }
fun getClassId(json: JsonObject?, key: String): Long? { fun getClassId(json: JsonObject?, key: String): Long? {
val team = json.getJsonObject(key) val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id") val teamId = team.getLong("Id")
val teamName = data.profile?.studentClassName val teamName = data.profile?.studentClassName
?: team.getString("Name") ?: team.getString("Name")
@ -148,7 +172,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
fun isCurrentYear(dateTime: Long): Boolean { fun isCurrentYear(dateTime: Long): Boolean {
return profile?.let { profile -> return profile?.let { profile ->
return@let dateTime >= profile.dateSemester1Start.inMillis return@let dateTime >= profile.dateSemester1Start.inMillis - WEEK * MS
} ?: false } ?: false
} }
@ -355,6 +379,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
dateTo: Date? = null, dateTo: Date? = null,
lastSync: Long? = null, lastSync: Long? = null,
folder: Int? = null, folder: Int? = null,
messageBox: String? = null,
params: Map<String, String> = mapOf(), params: Map<String, String> = mapOf(),
includeFilterType: Boolean = true, includeFilterType: Boolean = true,
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
@ -378,6 +403,9 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
query["periodId"] = data.studentSemesterId.toString() query["periodId"] = data.studentSemesterId.toString()
query["pupilId"] = data.studentId.toString() query["pupilId"] = data.studentId.toString()
} }
HebeFilterType.BY_MESSAGEBOX -> {
query["box"] = messageBox ?: data.messageBoxKey ?: ""
}
} }
if (dateFrom != null) if (dateFrom != null)

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
enum class HebeFilterType(val endpoint: String) { enum class HebeFilterType(val endpoint: String) {
BY_MESSAGEBOX("byBox"),
BY_PUPIL("byPupil"), BY_PUPIL("byPupil"),
BY_PERSON("byPerson"), BY_PERSON("byPerson"),
BY_PERIOD("byPeriod") BY_PERIOD("byPeriod")

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set import androidx.core.util.set
import androidx.room.OnConflictStrategy
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ADDRESSBOOK import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ADDRESSBOOK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
@ -44,7 +45,6 @@ class VulcanHebeAddressbook(
) { list, _ -> ) { list, _ ->
list.forEach { person -> list.forEach { person ->
val id = person.getString("Id") ?: return@forEach val id = person.getString("Id") ?: return@forEach
val loginId = person.getString("LoginId") ?: return@forEach
val idType = id.split("-") val idType = id.split("-")
.getOrNull(0) .getOrNull(0)
@ -69,7 +69,7 @@ class VulcanHebeAddressbook(
idLong, idLong,
name, name,
surname, surname,
loginId null
).also { ).also {
data.teacherList[idLong] = it data.teacherList[idLong] = it
} }
@ -108,13 +108,14 @@ class VulcanHebeAddressbook(
} }
teacher.setTeacherType(personType) teacher.setTeacherType(personType)
teacher.typeDescription = roleText if (roleText != null)
teacher.typeDescription = roleText
} }
if (teacher.type == 0) if (teacher.type == 0)
teacher.setTeacherType(typeBase) teacher.setTeacherType(typeBase)
} }
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY) data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK) onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK)
} }

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.room.OnConflictStrategy
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_OTHER
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENT
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
import pl.szczodrzynski.edziennik.ext.DAY
import pl.szczodrzynski.edziennik.ext.getString
class VulcanHebeAddressbook2(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeAddressbook2"
}
init {
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK,
HebeFilterType.BY_MESSAGEBOX,
messageBox = data.messageBoxKey,
lastSync = lastSync,
includeFilterType = false
) { list, _ ->
list.forEach { person ->
val teacher = getTeacherRecipient(person) ?: return@forEach
val group = person.getString("Group", "P")
if (teacher.type == TYPE_OTHER) {
teacher.type = when (group) {
"P" -> TYPE_TEACHER // Pracownik
"O" -> TYPE_PARENT // Opiekun
"U" -> TYPE_STUDENT // Uczeń
else -> TYPE_OTHER
}
}
}
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2, 2 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2)
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-9-16.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.ext.DAY
import pl.szczodrzynski.edziennik.ext.getString
class VulcanHebeMessageBoxes(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeMessageBoxes"
}
init {
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX,
lastSync = lastSync
) { list, _ ->
for (messageBox in list) {
val name = messageBox.getString("Name") ?: continue
val studentName = profile?.studentNameLong ?: continue
if (!name.startsWith(studentName))
continue
data.messageBoxKey = messageBox.getString("GlobalKey")
data.messageBoxName = name
break
}
data.setSyncNext(ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES, 7 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES)
}
}
}

View File

@ -4,22 +4,21 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_SENT import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.navlib.crc16
class VulcanHebeMessages( class VulcanHebeMessages(
override val data: DataVulcan, override val data: DataVulcan,
@ -27,29 +26,7 @@ class VulcanHebeMessages(
val onSuccess: (endpointId: Int) -> Unit val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) { ) : VulcanHebe(data, lastSync) {
companion object { companion object {
const val TAG = "VulcanHebeMessagesInbox" const val TAG = "VulcanHebeMessages"
}
private fun getPersonId(json: JsonObject): Long {
val senderLoginId = json.getInt("LoginId") ?: return -1
/*if (senderLoginId == data.studentLoginId)
return -1*/
val senderName = json.getString("Address") ?: return -1
val senderNameSplit = senderName.splitName()
val senderLoginIdStr = senderLoginId.toString()
val teacher = data.teacherList.singleOrNull { it.loginId == senderLoginIdStr }
?: Teacher(
profileId,
-1 * crc16(senderName).toLong(),
senderNameSplit?.second ?: "",
senderNameSplit?.first ?: "",
senderLoginIdStr
).also {
it.setTeacherType(Teacher.TYPE_OTHER)
data.teacherList[it.id] = it
}
return teacher.id
} }
fun getMessages(messageType: Int) { fun getMessages(messageType: Int) {
@ -64,17 +41,28 @@ class VulcanHebeMessages(
TYPE_SENT -> ENDPOINT_VULCAN_HEBE_MESSAGES_SENT TYPE_SENT -> ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
else -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX else -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
} }
val messageBox = data.messageBoxKey
if (messageBox == null) {
onSuccess(endpointId)
return
}
apiGetList( apiGetList(
TAG, TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES, VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES,
HebeFilterType.BY_PERSON, HebeFilterType.BY_MESSAGEBOX,
messageBox = data.messageBoxKey,
folder = folder, folder = folder,
lastSync = lastSync lastSync = lastSync
) { list, _ -> ) { list, _ ->
list.forEach { message -> list.forEach { message ->
val id = message.getLong("Id") ?: return@forEach val uuid = message.getString("Id") ?: return@forEach
val id = Utils.crc32(uuid.toByteArray())
val globalKey = message.getString("GlobalKey", "")
val threadKey = message.getString("ThreadKey", "")
val subject = message.getString("Subject") ?: return@forEach val subject = message.getString("Subject") ?: return@forEach
val body = message.getString("Content") ?: return@forEach var body = message.getString("Content") ?: return@forEach
val sender = message.getJsonObject("Sender") ?: return@forEach val sender = message.getJsonObject("Sender") ?: return@forEach
@ -83,13 +71,27 @@ class VulcanHebeMessages(
if (!isCurrentYear(sentDate)) return@forEach if (!isCurrentYear(sentDate)) return@forEach
val senderId = if (messageType == TYPE_RECEIVED)
getTeacherRecipient(sender)?.id
else
null
val meta = mutableMapOf(
"uuid" to uuid,
"globalKey" to globalKey,
"threadKey" to threadKey,
)
val metaString = meta.map { "${it.key}=${it.value}" }.join("&")
body = "[META:${metaString}]" + body
body = body.replace("\n", "<br>")
val messageObject = Message( val messageObject = Message(
profileId = profileId, profileId = profileId,
id = id, id = id,
type = messageType, type = messageType,
subject = subject, subject = subject,
body = body.replace("\n", "<br>"), body = body,
senderId = if (messageType == TYPE_RECEIVED) getPersonId(sender) else null, senderId = senderId,
addedDate = sentDate addedDate = sentDate
) )
@ -101,9 +103,14 @@ class VulcanHebeMessages(
else -1 else -1
for (receiver in receivers) { for (receiver in receivers) {
val recipientId = if (messageType == TYPE_SENT)
getTeacherRecipient(receiver)?.id ?: -1
else
-1
val messageRecipientObject = MessageRecipient( val messageRecipientObject = MessageRecipient(
profileId, profileId,
if (messageType == TYPE_SENT) getPersonId(receiver) else -1, recipientId,
-1, -1,
receiverReadDate, receiverReadDate,
id id
@ -115,6 +122,9 @@ class VulcanHebeMessages(
?.asJsonObjectList() ?.asJsonObjectList()
?: return@forEach ?: return@forEach
messageObject.attachmentIds = mutableListOf()
messageObject.attachmentNames = mutableListOf()
messageObject.attachmentSizes = mutableListOf()
for (attachment in attachments) { for (attachment in attachments) {
val fileName = attachment.getString("Name") ?: continue val fileName = attachment.getString("Name") ?: continue
val url = attachment.getString("Link") ?: continue val url = attachment.getString("Link") ?: continue

View File

@ -5,7 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
@ -23,13 +23,19 @@ class VulcanHebeMessagesChangeStatus(
const val TAG = "VulcanHebeMessagesChangeStatus" const val TAG = "VulcanHebeMessagesChangeStatus"
} }
init { init { let {
val messageKey = messageObject.body?.let { data.parseMessageMeta(it) }?.get("globalKey") ?: run {
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
return@let
}
apiPost( apiPost(
TAG, TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS, VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS,
payload = JsonObject( payload = JsonObject(
"MessageId" to messageObject.id, "BoxKey" to data.messageBoxKey,
"LoginId" to data.studentLoginId, "MessageKey" to messageKey,
"Status" to 1 "Status" to 1
) )
) { _: Boolean, _ -> ) { _: Boolean, _ ->
@ -61,5 +67,5 @@ class VulcanHebeMessagesChangeStatus(
EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess() onSuccess()
} }
} }}
} }

View File

@ -4,17 +4,20 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_MESSAGE_NOT_SENT
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_SEND
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ext.*
import java.util.UUID
class VulcanHebeSendMessage( class VulcanHebeSendMessage(
override val data: DataVulcan, override val data: DataVulcan,
@ -28,9 +31,9 @@ class VulcanHebeSendMessage(
} }
init { init {
if (data.senderAddressName == null || data.senderAddressHash == null) { if (data.messageBoxKey == null || data.messageBoxName == null) {
VulcanHebeMain(data).getStudents(data.profile, null) { VulcanHebeMessageBoxes(data, 0) {
if (data.senderAddressName == null || data.senderAddressHash == null) { if (data.messageBoxKey == null || data.messageBoxName == null) {
data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY) data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY)
} }
else { else {
@ -44,47 +47,64 @@ class VulcanHebeSendMessage(
} }
private fun sendMessage() { private fun sendMessage() {
val uuid = UUID.randomUUID().toString()
val globalKey = UUID.randomUUID().toString()
val partition = "${data.symbol}-${data.schoolSymbol}"
val recipientsArray = JsonArray() val recipientsArray = JsonArray()
recipients.forEach { teacher -> recipients.forEach { teacher ->
val loginId = teacher.loginId?.split(";", limit = 3) ?: return@forEach
val key = loginId.getOrNull(0) ?: teacher.loginId
val group = loginId.getOrNull(1)
val name = loginId.getOrNull(2)
if (key?.toIntOrNull() != null) {
// raise error for old-format (non-UUID) login IDs
data.error(TAG, ERROR_MESSAGE_NOT_SENT)
return
}
recipientsArray += JsonObject( recipientsArray += JsonObject(
"Address" to teacher.fullNameLastFirst, "Id" to "${data.messageBoxKey}-${key}",
"LoginId" to (teacher.loginId?.toIntOrNull() ?: return@forEach), "Partition" to partition,
"Initials" to teacher.initialsLastFirst, "Owner" to data.messageBoxKey,
"AddressHash" to teacher.fullNameLastFirst.sha1Hex() "GlobalKey" to key,
"Name" to name,
"Group" to group,
"Initials" to "",
"HasRead" to 0,
) )
} }
val senderName = (profile?.accountName ?: profile?.studentNameLong)
?.swapFirstLastName() ?: ""
val sender = JsonObject( val sender = JsonObject(
"Address" to data.senderAddressName, "Id" to "0",
"LoginId" to data.studentLoginId.toString(), "Partition" to partition,
"Initials" to senderName.getNameInitials(), "Owner" to data.messageBoxKey,
"AddressHash" to data.senderAddressHash "GlobalKey" to data.messageBoxKey,
"Name" to data.messageBoxName,
"Group" to "",
"Initials" to "",
"HasRead" to 0,
) )
apiPost( apiPost(
TAG, TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_SEND, VULCAN_HEBE_ENDPOINT_MESSAGEBOX_SEND,
payload = JsonObject( payload = JsonObject(
"Status" to 1, "Id" to uuid,
"Sender" to sender, "GlobalKey" to globalKey,
"DateSent" to null, "Partition" to partition,
"DateRead" to null, "ThreadKey" to globalKey, // TODO correct threadKey for reply messages
"Content" to text,
"Receiver" to recipientsArray,
"Id" to 0,
"Subject" to subject, "Subject" to subject,
"Attachments" to null, "Content" to text,
"Self" to null "Status" to 1,
"Owner" to data.messageBoxKey,
"DateSent" to buildDateTime(),
"DateRead" to null,
"Sender" to sender,
"Receiver" to recipientsArray,
"Attachments" to JsonArray(),
) )
) { json: JsonObject, _ -> ) { _: JsonObject, _ ->
val messageId = json.getLong("Id") // TODO handle errors
if (messageId == null) {
// TODO error
return@apiPost
}
VulcanHebeMessages(data, null) { VulcanHebeMessages(data, null) {
val message = data.messageList.firstOrNull { it.isSent && it.subject == subject } val message = data.messageList.firstOrNull { it.isSent && it.subject == subject }

View File

@ -45,6 +45,7 @@ class VulcanHebeTeachers(
when (subjectName) { when (subjectName) {
"Pedagog" -> teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE) "Pedagog" -> teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE)
"Dyrektor" -> teacher.setTeacherType(Teacher.TYPE_PRINCIPAL)
else -> { else -> {
val subjectId = data.getSubject(null, subjectName).id val subjectId = data.getSubject(null, subjectName).id
if (!teacher.subjects.contains(subjectId)) if (!teacher.subjects.contains(subjectId))

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.data.db.entity.*
@ -489,11 +490,19 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
teacherList[id] = this teacherList[id] = this
} }
return obj.also { return obj.also {
if (loginId != null && it.loginId != null) if (loginId != null)
it.loginId = loginId it.loginId = loginId
if (firstName.length > 1) if (firstName.length > 1)
it.name = firstName it.name = firstName
it.surname = lastName it.surname = lastName
} }
} }
fun parseMessageMeta(body: String): Map<String, String>? {
val match = MESSAGE_META.find(body) ?: return null
return match[1].split("&").associateBy(
{ it.substringBefore("=") },
{ it.substringAfter("=") },
)
}
} }

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.MTIzNDU2Nzg5MDWpYbYnaF===.$param2".sha256() return "$param1.MTIzNDU2Nzg5MDgEKON2ua===.$param2".sha256()
} }
} }

View File

@ -109,7 +109,7 @@ abstract class EventDao : BaseDao<Event, EventFull> {
abstract fun removeNotManual(profileId: Int)*/ abstract fun removeNotManual(profileId: Int)*/
@RawQuery @RawQuery
abstract fun dontKeepFuture(query: SupportSQLiteQuery?): Long abstract fun dontKeepFuture(query: SupportSQLiteQuery): Long
@Transaction @Transaction
open fun dontKeepFuture(profileId: Int, todayDate: Date, filter: String) { open fun dontKeepFuture(profileId: Int, todayDate: Date, filter: String) {

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -17,6 +18,8 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.databinding.TemplateFragmentBinding import pl.szczodrzynski.edziennik.databinding.TemplateFragmentBinding
import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener
import pl.szczodrzynski.edziennik.ui.base.lazypager.FragmentLazyPagerAdapter import pl.szczodrzynski.edziennik.ui.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class LabFragment : Fragment(), CoroutineScope { class LabFragment : Fragment(), CoroutineScope {
@ -26,7 +29,7 @@ class LabFragment : Fragment(), CoroutineScope {
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: AppCompatActivity
private lateinit var b: TemplateFragmentBinding private lateinit var b: TemplateFragmentBinding
private val job: Job = Job() private val job: Job = Job()
@ -36,11 +39,14 @@ class LabFragment : Fragment(), CoroutineScope {
// local/private variables go here // local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as AppCompatActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = TemplateFragmentBinding.inflate(inflater) b = TemplateFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout) when (activity) {
is MainActivity -> b.refreshLayout.setParent((activity as MainActivity).swipeRefreshLayout)
is LoginActivity -> b.refreshLayout.setParent((activity as LoginActivity).swipeRefreshLayout)
}
b.refreshLayout.isEnabled = false b.refreshLayout.isEnabled = false
return b.root return b.root
} }

View File

@ -9,6 +9,7 @@ import android.os.Process
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import com.chuckerteam.chucker.api.Chucker import com.chuckerteam.chucker.api.Chucker
@ -35,7 +36,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: AppCompatActivity
private lateinit var b: LabFragmentBinding private lateinit var b: LabFragmentBinding
private val job: Job = Job() private val job: Job = Job()
@ -45,7 +46,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
// local/private variables go here // local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as AppCompatActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = LabFragmentBinding.inflate(inflater) b = LabFragmentBinding.inflate(inflater)
@ -55,6 +56,16 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
b.app = app b.app = app
if (app.profile.id == 0) {
b.last10unseen.isVisible = false
b.fullSync.isVisible = false
b.clearProfile.isVisible = false
b.rodo.isVisible = false
b.removeHomework.isVisible = false
b.unarchive.isVisible = false
b.profile.isVisible = false
}
b.last10unseen.onClick { b.last10unseen.onClick {
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
val events = app.db.eventDao().getAllNow(App.profileId) val events = app.db.eventDao().getAllNow(App.profileId)
@ -139,7 +150,8 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) } b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) }
b.profile.select(app.profileId.toLong()) b.profile.select(app.profileId.toLong())
b.profile.setOnChangeListener { b.profile.setOnChangeListener {
activity.loadProfile(it.id.toInt()) if (activity is MainActivity)
(activity as MainActivity).loadProfile(it.id.toInt())
return@setOnChangeListener true return@setOnChangeListener true
} }

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -22,6 +23,7 @@ import pl.szczodrzynski.edziennik.ext.input
import pl.szczodrzynski.edziennik.ext.set import pl.szczodrzynski.edziennik.ext.set
import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.ext.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -31,7 +33,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: AppCompatActivity
private lateinit var b: TemplateListPageFragmentBinding private lateinit var b: TemplateListPageFragmentBinding
private val job: Job = Job() private val job: Job = Job()
@ -45,7 +47,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as AppCompatActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = TemplateListPageFragmentBinding.inflate(inflater) b = TemplateListPageFragmentBinding.inflate(inflater)
@ -142,7 +144,10 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
.show() .show()
} }
catch (e: Exception) { catch (e: Exception) {
activity.error(ApiError.fromThrowable(TAG, e)) if (activity is MainActivity)
(activity as MainActivity).error(ApiError.fromThrowable(TAG, e))
if (activity is LoginActivity)
(activity as LoginActivity).error(ApiError.fromThrowable(TAG, e))
} }
}) })

View File

@ -198,7 +198,7 @@ class HomeFragment : Fragment(), CoroutineScope {
} }
} }
override fun performAccessibilityAction(host: View, action: Int, args: Bundle): Boolean { override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
val fromPosition: Int = b.list.getChildLayoutPosition(host) val fromPosition: Int = b.list.getChildLayoutPosition(host)
if (action == R.id.move_card_down_action) { if (action == R.id.move_card_down_action) {
swapCards(fromPosition, fromPosition + 1, adapter) swapCards(fromPosition, fromPosition + 1, adapter)

View File

@ -75,7 +75,7 @@ class HomeTimetableCard(
private var counterEnd: Time? = null private var counterEnd: Time? = null
private var subjectSpannable: CharSequence? = null private var subjectSpannable: CharSequence? = null
private val ignoreCancelled = true private val ignoreCancelled = false
private val countInSeconds: Boolean private val countInSeconds: Boolean
get() = app.config.timetable.countInSeconds get() = app.config.timetable.countInSeconds
@ -160,6 +160,9 @@ class HomeTimetableCard(
&& it.displayEndTime > now && it.displayEndTime > now
&& !(it.isCancelled && ignoreCancelled) && !(it.isCancelled && ignoreCancelled)
} }
if (!ignoreCancelled && lessons.all { it.isCancelled })
// skip current day if all lessons are cancelled
lessons = listOf()
while ((lessons.isEmpty() || lessons.none { while ((lessons.isEmpty() || lessons.none {
it.type != Lesson.TYPE_NO_LESSONS it.type != Lesson.TYPE_NO_LESSONS
&& (it.displayDate != today && (it.displayDate != today
@ -174,6 +177,7 @@ class HomeTimetableCard(
it.profileId == profile.id it.profileId == profile.id
&& it.displayDate == timetableDate && it.displayDate == timetableDate
} }
lessons = lessons.dropWhile { it.isCancelled }
if (lessons.isEmpty()) if (lessons.isEmpty())
break break
@ -238,15 +242,18 @@ class HomeTimetableCard(
b.counter.visibility = View.GONE b.counter.visibility = View.GONE
val now = syncedNow val now = syncedNow
val firstLesson = lessons.firstOrNull() val firstLesson = lessons.firstOrNull { !it.isCancelled }
val lastLesson = lessons.lastOrNull() val lastLesson = lessons.lastOrNull { !it.isCancelled }
val skipFirst = if (firstLesson == null) 0 else lessons.indexOf(firstLesson)
val skipLast = if (lastLesson == null) 0 else lessons.size - 1 - lessons.indexOf(lastLesson)
val lessonCount = lessons.size - skipFirst - skipLast
if (isToday) { if (isToday) {
// today // today
b.dayInfo.setText(R.string.home_timetable_today) b.dayInfo.setText(R.string.home_timetable_today)
b.lessonInfo.setText( b.lessonInfo.setText(
R.string.home_timetable_lessons_remaining, R.string.home_timetable_lessons_remaining,
lessons.size, lessonCount,
lastLesson?.displayEndTime?.stringHM ?: "?" lastLesson?.displayEndTime?.stringHM ?: "?"
) )
counterStart = firstLesson?.displayStartTime counterStart = firstLesson?.displayStartTime
@ -291,12 +298,14 @@ class HomeTimetableCard(
b.dayInfo.setText(dayInfoRes, Week.getFullDayName(timetableDate.weekDay), timetableDate.formattedString) b.dayInfo.setText(dayInfoRes, Week.getFullDayName(timetableDate.weekDay), timetableDate.formattedString)
b.lessonInfo.setText( b.lessonInfo.setText(
R.string.home_timetable_lessons_info, R.string.home_timetable_lessons_info,
lessons.size, lessonCount,
firstLesson?.displayStartTime?.stringHM ?: "?", firstLesson?.displayStartTime?.stringHM ?: "?",
lastLesson?.displayEndTime?.stringHM ?: "?" lastLesson?.displayEndTime?.stringHM ?: "?"
) )
b.lessonBig.setText(R.string.home_timetable_lesson_first, firstLesson.subjectSpannable) b.lessonBig.setText(R.string.home_timetable_lesson_first, firstLesson.subjectSpannable)
b.counter.visibility = View.VISIBLE
b.counter.text = firstLesson?.displayStartTime?.stringHM
firstLesson?.displayClassroom?.let { firstLesson?.displayClassroom?.let {
b.classroom.visibility = View.VISIBLE b.classroom.visibility = View.VISIBLE
b.classroom.text = it b.classroom.text = it
@ -308,9 +317,8 @@ class HomeTimetableCard(
val text = mutableListOf<CharSequence>( val text = mutableListOf<CharSequence>(
activity.getString(R.string.home_timetable_later) activity.getString(R.string.home_timetable_later)
) )
var first = true val nextLessons = lessons.drop(skipFirst + 1)
for (lesson in lessons) { for (lesson in nextLessons) {
if (first) { first = false; continue }
text += listOf( text += listOf(
lesson.displayStartTime?.stringHM, lesson.displayStartTime?.stringHM,
lesson.subjectSpannable lesson.subjectSpannable
@ -363,7 +371,10 @@ class HomeTimetableCard(
b.progress.visibility = View.GONE b.progress.visibility = View.GONE
b.counter.visibility = View.VISIBLE b.counter.visibility = View.VISIBLE
val diff = counterStart - now val diff = counterStart - now
b.counter.text = activity.timeTill(diff.toInt(), "\n", countInSeconds) if (diff >= 60 * MINUTE)
b.counter.text = counterStart.stringHM
else
b.counter.text = activity.timeTill(diff.toInt(), "\n", countInSeconds)
} }
else { else {
// the lesson is right now // the lesson is right now

View File

@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.databinding.LoginActivityBinding import pl.szczodrzynski.edziennik.databinding.LoginActivityBinding
import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class LoginActivity : AppCompatActivity(), CoroutineScope { class LoginActivity : AppCompatActivity(), CoroutineScope {
@ -32,6 +33,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
lateinit var navOptions: NavOptions lateinit var navOptions: NavOptions
val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) } val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext

View File

@ -66,6 +66,8 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return if (!isAdded) return
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
b.versionText.setText( b.versionText.setText(
R.string.login_chooser_version_format, R.string.login_chooser_version_format,
app.buildManager.versionName, app.buildManager.versionName,
@ -73,10 +75,33 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
) )
b.versionText.onClick { b.versionText.onClick {
app.buildManager.showVersionDialog(activity) app.buildManager.showVersionDialog(activity)
if (!App.devMode)
return@onClick
if (adapter.items.firstOrNull { it is LoginInfo.Register && it.internalName == "lab" } != null)
return@onClick
adapter.items.add(
index = 0,
element = LoginInfo.Register(
loginType = 999999,
internalName = "lab",
registerName = R.string.menu_lab,
registerLogo = R.drawable.face_2,
loginModes = listOf(
LoginInfo.Mode(
loginMode = 0,
name = 0,
icon = 0,
guideText = 0,
credentials = listOf(),
errorCodes = mapOf()
)
)
)
)
adapter.notifyItemInserted(0)
b.list.smoothScrollToPosition(0)
} }
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
LoginInfo.chooserList = LoginInfo.chooserList LoginInfo.chooserList = LoginInfo.chooserList
?: LoginInfo.list.toMutableList() ?: LoginInfo.list.toMutableList()
@ -164,7 +189,7 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
anim.fillAfter = true anim.fillAfter = true
activity.getRootView().startAnimation(anim) activity.getRootView().startAnimation(anim)
b.list.smoothScrollToPosition(0) adapter.items.removeAll { it !is LoginInfo.Register }
adapter.items.add( adapter.items.add(
LoginInfo.Register( LoginInfo.Register(
loginType = 74, loginType = 74,
@ -183,7 +208,8 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
) )
) )
) )
adapter.notifyItemInserted(adapter.items.size - 1) adapter.notifyDataSetChanged()
b.list.smoothScrollToPosition(adapter.items.size - 1)
} }
when { when {
@ -216,6 +242,11 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
return return
} }
if (loginType.internalName == "lab") {
nav.navigate(R.id.labFragment, null, activity.navOptions)
return
}
if (!app.config.privacyPolicyAccepted) { if (!app.config.privacyPolicyAccepted) {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle(R.string.privacy_policy) .setTitle(R.string.privacy_policy)

View File

@ -77,7 +77,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
private lateinit var stylingConfig: StylingConfig private lateinit var stylingConfig: StylingConfig
private lateinit var uiConfig: UIConfig private lateinit var uiConfig: UIConfig
private val enableTextStyling private val enableTextStyling
get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS
private var changedRecipients = false private var changedRecipients = false
private var changedSubject = false private var changedSubject = false
private var changedBody = false private var changedBody = false

View File

@ -15,6 +15,7 @@ import android.text.style.*
import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META
import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.getWordBounds import pl.szczodrzynski.edziennik.ext.getWordBounds
import pl.szczodrzynski.edziennik.ext.resolveAttr import pl.szczodrzynski.edziennik.ext.resolveAttr
@ -45,7 +46,7 @@ object BetterHtml {
.toRegex(RegexOption.IGNORE_CASE) .toRegex(RegexOption.IGNORE_CASE)
var text = html var text = html
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "") .replace(MESSAGE_META, "")
.replace("background-color: ?$hexPattern;".toRegex(), "") .replace("background-color: ?$hexPattern;".toRegex(), "")
// treat paragraphs as if they had no margin // treat paragraphs as if they had no margin
.replace("<p", "<span") .replace("<p", "<span")

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -19,13 +19,19 @@
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"> android:visibility="visible">
<fragment <pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
android:id="@+id/nav_host_fragment" android:id="@+id/swipeRefreshLayout"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
app:defaultNavHost="false"
app:navGraph="@navigation/nav_login" /> <fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:navGraph="@navigation/nav_login" />
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@ -22,6 +22,9 @@
<action <action
android:id="@+id/action_loginChooserFragment_to_loginEggsFragment" android:id="@+id/action_loginChooserFragment_to_loginEggsFragment"
app:destination="@id/loginEggsFragment" /> app:destination="@id/loginEggsFragment" />
<action
android:id="@+id/action_loginChooserFragment_to_labFragment"
app:destination="@id/labFragment" />
</fragment> </fragment>
<!-- eggs --> <!-- eggs -->
<fragment <fragment
@ -95,4 +98,8 @@
android:id="@+id/loginFinishFragment" android:id="@+id/loginFinishFragment"
android:name="pl.szczodrzynski.edziennik.ui.login.LoginFinishFragment" android:name="pl.szczodrzynski.edziennik.ui.login.LoginFinishFragment"
android:label="LoginFinishFragment" /> android:label="LoginFinishFragment" />
<fragment
android:id="@+id/labFragment"
android:name="pl.szczodrzynski.edziennik.ui.debug.LabFragment"
android:label="LabFragment" />
</navigation> </navigation>

View File

@ -859,7 +859,7 @@
<string name="settings_about_licenses_text">Open-source licenses</string> <string name="settings_about_licenses_text">Open-source licenses</string>
<string name="settings_about_privacy_policy_text">Privacy policy</string> <string name="settings_about_privacy_policy_text">Privacy policy</string>
<string name="settings_card_register_title">E-register</string> <string name="settings_card_register_title">E-register</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - 2022</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 2022</string>
<string name="settings_about_update_subtext">Click to check for updates</string> <string name="settings_about_update_subtext">Click to check for updates</string>
<string name="settings_about_update_text">Update</string> <string name="settings_about_update_text">Update</string>
<string name="settings_about_version_text">Version</string> <string name="settings_about_version_text">Version</string>

View File

@ -2,17 +2,17 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.5.30' kotlin_version = '1.6.10'
release = [ release = [
versionName: "4.11.5", versionName: "4.11.7",
versionCode: 4110599 versionCode: 4110799
] ]
setup = [ setup = [
compileSdk: 30, compileSdk: 33,
minSdk : 16, minSdk : 16,
targetSdk : 30 targetSdk : 33
] ]
} }
@ -21,10 +21,10 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
} }
} }

View File

@ -1,6 +1,6 @@
#Wed Feb 17 14:04:38 CET 2021 #Wed Feb 17 14:04:38 CET 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME