Compare commits

...

12 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
0f90430387 [4.11.5] Update build.gradle, signing and changelog. 2022-04-18 11:31:24 +02:00
745523c620 [UI] Fix touch events on swipe refresh pages (API 32+). (#129)
* [Hotfix] Fix touch events on swipe refresh pages (API 32)

* Add API SDK version check
2022-04-18 11:28:34 +02:00
8911ce2bc1 [4.11.4] Update build.gradle, signing and changelog. 2022-03-14 17:45:11 +01:00
2990fc5479 [Firebase] Disable notifications for past shared events. 2022-03-14 17:08:35 +01:00
48b7adb564 [UI/Grades] Group unknown subjects without specified name. 2022-03-14 17:05:16 +01:00
49 changed files with 580 additions and 179 deletions

View File

@ -82,9 +82,6 @@ android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
lintOptions {
checkReleaseBuilds = false
}
buildFeatures {
dataBinding = true
viewBinding = true
@ -98,7 +95,9 @@ android {
jvmTarget = "1.8"
}
packagingOptions {
exclude 'META-INF/library-core_release.kotlin_module'
resources {
excludes += ['META-INF/library-core_release.kotlin_module']
}
}
externalNativeBuild {
cmake {
@ -106,6 +105,9 @@ android {
version "3.10.2"
}
}
lint {
checkReleaseBuilds false
}
}
tasks.whenTaskAdded { task ->
@ -140,28 +142,29 @@ dependencies {
// Language cores
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"
// 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.constraintlayout:constraintlayout:2.1.1"
implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.room:room-runtime:2.3.0"
implementation "androidx.work:work-runtime-ktx:2.6.0"
kapt "androidx.room:room-compiler:2.3.0"
implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.work:work-runtime-ktx:2.7.1"
kapt "androidx.room:room-compiler:2.4.3"
// 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"
// Play Services/Firebase
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-crashlytics:18.2.3"
implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } }
implementation "com.google.firebase:firebase-crashlytics:18.2.13"
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
// OkHttp, Retrofit, Gson, Jsoup

View File

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

View File

@ -1,6 +1,8 @@
<h3>Wersja 4.11.3, 2022-02-21</h3>
<h3>Wersja 4.11.7, 2022-09-17</h3>
<ul>
<li>Naprawiono odświeżanie planu lekcji po pobraniu wybranego tygodnia.</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>
<br>
<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0x90, 0xe9, 0x2f, 0x20, 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);

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_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_SUFFIX = " - nie usuwać"
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_NOTICES = "api/mobile/note"
const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson"
const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status"
const val VULCAN_HEBE_ENDPOINT_MESSAGES_SEND = "api/mobile/message"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX = "api/mobile/messagebox"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK = "api/mobile/messagebox/addressbook"
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 EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -20,6 +20,10 @@ object Regexes {
"""<br\s?/?>""".toRegex()
}
val MESSAGE_META by lazy {
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
}
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 }
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
private var mSenderAddressHash: String? = null
var senderAddressHash: String?
get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash }
set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value }
private var mMessageBoxKey: String? = null
var messageBoxKey: String?
get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey }
set(value) { profile?.putStudentData("messageBoxKey", value) ?: return; mMessageBoxKey = value }
private var mSenderAddressName: String? = null
var senderAddressName: String?
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName }
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value }
private var mMessageBoxName: String? = null
var messageBoxName: String?
get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName }
set(value) { profile?.putStudentData("messageBoxName", value) ?: return; mMessageBoxName = value }
val apiUrl: String?
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_PUSH_CONFIG = 3005
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_EXAMS = 3030
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_NOTICES = 3070
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_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(
// timetable
@ -85,6 +87,8 @@ val VulcanFeatures = listOf(
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_VULCAN_HEBE_MAIN 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))
)

View File

@ -21,10 +21,12 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
ENDPOINT_VULCAN_HEBE_MAIN,
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2,
ENDPOINT_VULCAN_HEBE_TIMETABLE,
ENDPOINT_VULCAN_HEBE_EXAMS,
ENDPOINT_VULCAN_HEBE_HOMEWORK,
ENDPOINT_VULCAN_HEBE_NOTICES,
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES,
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
ENDPOINT_VULCAN_HEBE_TEACHERS,
@ -107,6 +109,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_addressbook)
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 -> {
data.startProgress(R.string.edziennik_progress_endpoint_teachers)
VulcanHebeTeachers(data, lastSync, onSuccess)
@ -139,6 +145,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
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 -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
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.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.hebe.getSignatureHeaders
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
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
}
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? {
val date = json.getJsonObject(key)
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
}
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? {
val subject = json.getJsonObject(key)
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? {
val team = json.getJsonObject(key)
val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id")
var teamName = team.getString("Shortcut")
?: 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? {
val team = json.getJsonObject(key)
val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id")
val teamName = data.profile?.studentClassName
?: team.getString("Name")
@ -148,7 +172,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
fun isCurrentYear(dateTime: Long): Boolean {
return profile?.let { profile ->
return@let dateTime >= profile.dateSemester1Start.inMillis
return@let dateTime >= profile.dateSemester1Start.inMillis - WEEK * MS
} ?: false
}
@ -355,6 +379,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
dateTo: Date? = null,
lastSync: Long? = null,
folder: Int? = null,
messageBox: String? = null,
params: Map<String, String> = mapOf(),
includeFilterType: Boolean = true,
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["pupilId"] = data.studentId.toString()
}
HebeFilterType.BY_MESSAGEBOX -> {
query["box"] = messageBox ?: data.messageBoxKey ?: ""
}
}
if (dateFrom != null)

View File

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

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set
import androidx.room.OnConflictStrategy
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ADDRESSBOOK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
@ -44,7 +45,6 @@ class VulcanHebeAddressbook(
) { list, _ ->
list.forEach { person ->
val id = person.getString("Id") ?: return@forEach
val loginId = person.getString("LoginId") ?: return@forEach
val idType = id.split("-")
.getOrNull(0)
@ -69,7 +69,7 @@ class VulcanHebeAddressbook(
idLong,
name,
surname,
loginId
null
).also {
data.teacherList[idLong] = it
}
@ -108,13 +108,14 @@ class VulcanHebeAddressbook(
}
teacher.setTeacherType(personType)
teacher.typeDescription = roleText
if (roleText != null)
teacher.typeDescription = roleText
}
if (teacher.type == 0)
teacher.setTeacherType(typeBase)
}
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
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
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.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.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.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_RECEIVED
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.utils.Utils
import pl.szczodrzynski.navlib.crc16
class VulcanHebeMessages(
override val data: DataVulcan,
@ -27,29 +26,7 @@ class VulcanHebeMessages(
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeMessagesInbox"
}
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
const val TAG = "VulcanHebeMessages"
}
fun getMessages(messageType: Int) {
@ -64,17 +41,28 @@ class VulcanHebeMessages(
TYPE_SENT -> ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
else -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
}
val messageBox = data.messageBoxKey
if (messageBox == null) {
onSuccess(endpointId)
return
}
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES,
HebeFilterType.BY_PERSON,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES,
HebeFilterType.BY_MESSAGEBOX,
messageBox = data.messageBoxKey,
folder = folder,
lastSync = lastSync
) { list, _ ->
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 body = message.getString("Content") ?: return@forEach
var body = message.getString("Content") ?: return@forEach
val sender = message.getJsonObject("Sender") ?: return@forEach
@ -83,13 +71,27 @@ class VulcanHebeMessages(
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(
profileId = profileId,
id = id,
type = messageType,
subject = subject,
body = body.replace("\n", "<br>"),
senderId = if (messageType == TYPE_RECEIVED) getPersonId(sender) else null,
body = body,
senderId = senderId,
addedDate = sentDate
)
@ -101,9 +103,14 @@ class VulcanHebeMessages(
else -1
for (receiver in receivers) {
val recipientId = if (messageType == TYPE_SENT)
getTeacherRecipient(receiver)?.id ?: -1
else
-1
val messageRecipientObject = MessageRecipient(
profileId,
if (messageType == TYPE_SENT) getPersonId(receiver) else -1,
recipientId,
-1,
receiverReadDate,
id
@ -115,6 +122,9 @@ class VulcanHebeMessages(
?.asJsonObjectList()
?: return@forEach
messageObject.attachmentIds = mutableListOf()
messageObject.attachmentNames = mutableListOf()
messageObject.attachmentSizes = mutableListOf()
for (attachment in attachments) {
val fileName = attachment.getString("Name") ?: continue
val url = attachment.getString("Link") ?: continue

View File

@ -5,7 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
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.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
@ -23,13 +23,19 @@ class 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(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS,
payload = JsonObject(
"MessageId" to messageObject.id,
"LoginId" to data.studentLoginId,
"BoxKey" to data.messageBoxKey,
"MessageKey" to messageKey,
"Status" to 1
)
) { _: Boolean, _ ->
@ -61,5 +67,5 @@ class VulcanHebeMessagesChangeStatus(
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
}
}}
}

View File

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

View File

@ -45,6 +45,7 @@ class VulcanHebeTeachers(
when (subjectName) {
"Pedagog" -> teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE)
"Dyrektor" -> teacher.setTeacherType(Teacher.TYPE_PRINCIPAL)
else -> {
val subjectId = data.getSubject(null, subjectName).id
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.R
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.db.AppDb
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
}
return obj.also {
if (loginId != null && it.loginId != null)
if (loginId != null)
it.loginId = loginId
if (firstName.length > 1)
it.name = firstName
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 pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDw/KY+My+===.$param2".sha256()
return "$param1.MTIzNDU2Nzg5MDgEKON2ua===.$param2".sha256()
}
}

View File

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

View File

@ -164,7 +164,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
val type = if (event.isHomework) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
if (!notificationFilter.contains(type) && event.sharedBy != "self") {
if (!notificationFilter.contains(type) && event.sharedBy != "self" && event.date >= Date.getToday()) {
val notification = Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -17,6 +18,8 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.databinding.TemplateFragmentBinding
import pl.szczodrzynski.edziennik.ext.addOnPageSelectedListener
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
class LabFragment : Fragment(), CoroutineScope {
@ -26,7 +29,7 @@ class LabFragment : Fragment(), CoroutineScope {
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var activity: AppCompatActivity
private lateinit var b: TemplateFragmentBinding
private val job: Job = Job()
@ -36,11 +39,14 @@ class LabFragment : Fragment(), CoroutineScope {
// local/private variables go here
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
app = activity.application as App
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
return b.root
}

View File

@ -9,6 +9,7 @@ import android.os.Process
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.sqlite.db.SimpleSQLiteQuery
import com.chuckerteam.chucker.api.Chucker
@ -35,7 +36,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var activity: AppCompatActivity
private lateinit var b: LabFragmentBinding
private val job: Job = Job()
@ -45,7 +46,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
// local/private variables go here
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
app = activity.application as App
b = LabFragmentBinding.inflate(inflater)
@ -55,6 +56,16 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
override fun onPageCreated(): Boolean {
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 {
launch(Dispatchers.Default) {
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.select(app.profileId.toLong())
b.profile.setOnChangeListener {
activity.loadProfile(it.id.toInt())
if (activity is MainActivity)
(activity as MainActivity).loadProfile(it.id.toInt())
return@setOnChangeListener true
}

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
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.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
@ -31,7 +33,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var activity: AppCompatActivity
private lateinit var b: TemplateListPageFragmentBinding
private val job: Job = Job()
@ -45,7 +47,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
}
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
app = activity.application as App
b = TemplateListPageFragmentBinding.inflate(inflater)
@ -142,7 +144,10 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
.show()
}
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

@ -36,6 +36,7 @@ class GradesAdapter(
private const val ITEM_TYPE_EMPTY = 2
private const val ITEM_TYPE_GRADE = 3
private const val ITEM_TYPE_STATS = 4
private const val ITEM_TYPE_UNKNOWN_SUBJECT = 5
const val STATE_CLOSED = 0
const val STATE_OPENED = 1
}
@ -58,6 +59,7 @@ class GradesAdapter(
ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent)
ITEM_TYPE_GRADE -> GradeViewHolder(inflater, parent)
ITEM_TYPE_STATS -> StatsViewHolder(inflater, parent)
ITEM_TYPE_UNKNOWN_SUBJECT -> UnknownSubjectViewHolder(inflater, parent)
else -> throw IllegalArgumentException("Incorrect viewType")
}
}
@ -69,6 +71,7 @@ class GradesAdapter(
is GradesEmpty -> ITEM_TYPE_EMPTY
is Grade -> ITEM_TYPE_GRADE
is GradesStats -> ITEM_TYPE_STATS
is GradesUnknownSubject -> ITEM_TYPE_UNKNOWN_SUBJECT
else -> throw IllegalArgumentException("Incorrect viewType")
}
}
@ -86,7 +89,7 @@ class GradesAdapter(
fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) {
model ?: return
val position = items.indexOf(model)
var position = items.indexOf(model)
if (position == -1)
return
@ -138,9 +141,16 @@ class GradesAdapter(
else -> model.items
}
if (model is GradesSubject && model.isUnknown) {
position++
items.add(position, GradesUnknownSubject())
if (notifyAdapter) notifyItemInserted(position)
}
position++
model.state = STATE_OPENED
items.addAll(position + 1, subItems.filterNotNull())
if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size)
items.addAll(position, subItems.filterNotNull())
if (notifyAdapter) notifyItemRangeInserted(position, subItems.size)
if (model is GradesSubject) {
// auto expand first semester
@ -156,9 +166,10 @@ class GradesAdapter(
else -> semester.grades
}
position++
semester.state = STATE_OPENED
items.addAll(position + 2 + semesterIndex, grades)
if (notifyAdapter) notifyItemRangeInserted(position + 2 + semesterIndex, grades.size)
items.addAll(position + semesterIndex, grades)
if (notifyAdapter) notifyItemRangeInserted(position + semesterIndex, grades.size)
}
}
}
@ -198,6 +209,7 @@ class GradesAdapter(
is EmptyViewHolder -> ITEM_TYPE_EMPTY
is GradeViewHolder -> ITEM_TYPE_GRADE
is StatsViewHolder -> ITEM_TYPE_STATS
is UnknownSubjectViewHolder -> ITEM_TYPE_UNKNOWN_SUBJECT
else -> throw IllegalArgumentException("Incorrect viewType")
}
holder.itemView.setTag(R.string.tag_key_view_type, viewType)
@ -210,6 +222,7 @@ class GradesAdapter(
holder is EmptyViewHolder && item is GradesEmpty -> holder.onBind(activity, app, item, position, this)
holder is GradeViewHolder && item is GradeFull -> holder.onBind(activity, app, item, position, this)
holder is StatsViewHolder && item is GradesStats -> holder.onBind(activity, app, item, position, this)
holder is UnknownSubjectViewHolder && item is GradesUnknownSubject -> holder.onBind(activity, app, item, position, this)
}
if (holder is SemesterViewHolder && item is GradesSemester) {

View File

@ -182,6 +182,7 @@ class GradesListFragment : Fragment(), CoroutineScope {
@Suppress("SuspendFunctionOnCoroutineScope")
private fun processGrades(grades: List<GradeFull>): MutableList<Any> {
val items = mutableListOf<GradesSubject>()
var unknownSubjectItem: GradesSubject? = null
var subjectId = -1L
var semesterNumber = 0
@ -200,17 +201,31 @@ class GradesListFragment : Fragment(), CoroutineScope {
subjectId = grade.subjectId
semesterNumber = 0
subject = items.firstOrNull { it.subjectId == subjectId }
?: GradesSubject(grade.subjectId, grade.subjectLongName ?: "").also {
subject = items.firstOrNull { it.subjectId == subjectId } ?: run {
if (grade.subjectLongName != null) {
return@run GradesSubject(grade.subjectId, grade.subjectLongName!!).also {
items += it
it.semester = 2
}
}
if (unknownSubjectItem == null) {
unknownSubjectItem = GradesSubject(-1, "unknown").also {
items += it
it.semester = 2
it.isUnknown = true
}
}
return@run unknownSubjectItem!!
}
}
if (grade.semester != semesterNumber) {
semesterNumber = grade.semester
semester = subject.semesters.firstOrNull { it.number == semesterNumber }
?: GradesSemester(subject.subjectId, grade.semester).also { subject.semesters += it }
?: GradesSemester(subject.subjectId, grade.semester).also {
subject.semesters += it
it.hideEditor = subject.isUnknown
}
}
grade.showAsUnseen = !grade.seen
@ -221,6 +236,11 @@ class GradesListFragment : Fragment(), CoroutineScope {
semester.hasUnseen = true
}
if (subject.isUnknown) {
// unknown subjects may have final grades (i.e. Mobidziennik)
grade.type = Grade.TYPE_NORMAL
}
when (grade.type) {
Grade.TYPE_SEMESTER1_PROPOSED,
Grade.TYPE_SEMESTER2_PROPOSED -> semester.proposedGrade = grade
@ -255,6 +275,10 @@ class GradesListFragment : Fragment(), CoroutineScope {
val yearlyPoint = mutableListOf<Float>()
for (item in items) {
if (item.isUnknown) {
// do not count averages for "unknown" subjects
continue
}
item.semesters.forEach { sem ->
manager.calculateAverages(sem.averages)
if (sem.number == 1) {

View File

@ -14,6 +14,7 @@ data class GradesSemester(
override var level = 2
var hasUnseen = false
var hideEditor = false
val averages = GradesAverages()
var proposedGrade: GradeFull? = null

View File

@ -15,6 +15,7 @@ data class GradesSubject(
var lastAddedDate = 0L
var semester: Int = 1
var isUnknown = false
var hasUnseen: Boolean = false
get() = field || semesters.any { it.hasUnseen }

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-3-14.
*/
package pl.szczodrzynski.edziennik.ui.grades.models
class GradesUnknownSubject

View File

@ -61,6 +61,8 @@ class SemesterViewHolder(
}
}
b.editButton.isVisible = !item.hideEditor
b.average.text = manager.getAverageString(app, item.averages)
b.proposedGrade.setGrade(item.proposedGrade, manager)
b.finalGrade.setGrade(item.finalGrade, manager)

View File

@ -19,9 +19,7 @@ import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.GradesItemSubjectBinding
import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.resolveAttr
import pl.szczodrzynski.edziennik.ext.setText
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.grades.GradeView
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter.Companion.STATE_CLOSED
@ -41,7 +39,12 @@ class SubjectViewHolder(
val manager = app.gradesManager
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
b.subjectName.text = item.subjectName
if (!item.isUnknown) {
b.subjectName.text = item.subjectName
}
else {
b.subjectName.text = R.string.grades_subject_unknown.resolveString(activity).asItalicSpannable()
}
b.dropdownIcon.rotation = when (item.state) {
STATE_CLOSED -> 0f
else -> 180f

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-3-14.
*/
package pl.szczodrzynski.edziennik.ui.grades.viewholder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.GradesItemUnknownSubjectBinding
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.grades.models.GradesUnknownSubject
class UnknownSubjectViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: GradesItemUnknownSubjectBinding = GradesItemUnknownSubjectBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesUnknownSubject, GradesAdapter> {
companion object {
private const val TAG = "UnknownSubjectViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesUnknownSubject, position: Int, adapter: GradesAdapter) {
}
}

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)
if (action == R.id.move_card_down_action) {
swapCards(fromPosition, fromPosition + 1, adapter)

View File

@ -85,6 +85,9 @@ class HomeGradesCard(
grades.forEach { grade ->
val model = ItemGradesSubjectModel.searchModelBySubjectId(subjects, grade.subjectId)
?: run {
if (grade.subjectLongName == null) {
return@forEach
}
subjects.add(ItemGradesSubjectModel(
profile,
Subject(profile.id, grade.subjectId, grade.subjectLongName, grade.subjectShortName),

View File

@ -75,7 +75,7 @@ class HomeTimetableCard(
private var counterEnd: Time? = null
private var subjectSpannable: CharSequence? = null
private val ignoreCancelled = true
private val ignoreCancelled = false
private val countInSeconds: Boolean
get() = app.config.timetable.countInSeconds
@ -160,6 +160,9 @@ class HomeTimetableCard(
&& it.displayEndTime > now
&& !(it.isCancelled && ignoreCancelled)
}
if (!ignoreCancelled && lessons.all { it.isCancelled })
// skip current day if all lessons are cancelled
lessons = listOf()
while ((lessons.isEmpty() || lessons.none {
it.type != Lesson.TYPE_NO_LESSONS
&& (it.displayDate != today
@ -174,6 +177,7 @@ class HomeTimetableCard(
it.profileId == profile.id
&& it.displayDate == timetableDate
}
lessons = lessons.dropWhile { it.isCancelled }
if (lessons.isEmpty())
break
@ -238,15 +242,18 @@ class HomeTimetableCard(
b.counter.visibility = View.GONE
val now = syncedNow
val firstLesson = lessons.firstOrNull()
val lastLesson = lessons.lastOrNull()
val firstLesson = lessons.firstOrNull { !it.isCancelled }
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) {
// today
b.dayInfo.setText(R.string.home_timetable_today)
b.lessonInfo.setText(
R.string.home_timetable_lessons_remaining,
lessons.size,
lessonCount,
lastLesson?.displayEndTime?.stringHM ?: "?"
)
counterStart = firstLesson?.displayStartTime
@ -291,12 +298,14 @@ class HomeTimetableCard(
b.dayInfo.setText(dayInfoRes, Week.getFullDayName(timetableDate.weekDay), timetableDate.formattedString)
b.lessonInfo.setText(
R.string.home_timetable_lessons_info,
lessons.size,
lessonCount,
firstLesson?.displayStartTime?.stringHM ?: "?",
lastLesson?.displayEndTime?.stringHM ?: "?"
)
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 {
b.classroom.visibility = View.VISIBLE
b.classroom.text = it
@ -308,9 +317,8 @@ class HomeTimetableCard(
val text = mutableListOf<CharSequence>(
activity.getString(R.string.home_timetable_later)
)
var first = true
for (lesson in lessons) {
if (first) { first = false; continue }
val nextLessons = lessons.drop(skipFirst + 1)
for (lesson in nextLessons) {
text += listOf(
lesson.displayStartTime?.stringHM,
lesson.subjectSpannable
@ -363,7 +371,10 @@ class HomeTimetableCard(
b.progress.visibility = View.GONE
b.counter.visibility = View.VISIBLE
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 {
// 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.databinding.LoginActivityBinding
import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import kotlin.coroutines.CoroutineContext
class LoginActivity : AppCompatActivity(), CoroutineScope {
@ -32,6 +33,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
lateinit var navOptions: NavOptions
val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
private val job: Job = Job()
override val coroutineContext: CoroutineContext

View File

@ -66,6 +66,8 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
b.versionText.setText(
R.string.login_chooser_version_format,
app.buildManager.versionName,
@ -73,10 +75,33 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
)
b.versionText.onClick {
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.list.toMutableList()
@ -164,7 +189,7 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
b.list.smoothScrollToPosition(0)
adapter.items.removeAll { it !is LoginInfo.Register }
adapter.items.add(
LoginInfo.Register(
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 {
@ -216,6 +242,11 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
return
}
if (loginType.internalName == "lab") {
nav.navigate(R.id.labFragment, null, activity.navOptions)
return
}
if (!app.config.privacyPolicyAccepted) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.privacy_policy)

View File

@ -77,7 +77,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
private lateinit var stylingConfig: StylingConfig
private lateinit var uiConfig: UIConfig
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 changedSubject = false
private var changedBody = false

View File

@ -17,6 +17,7 @@
package pl.szczodrzynski.edziennik.utils;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@ -83,7 +84,9 @@ public class SwipeRefreshLayoutNoIndicator extends SwipeRefreshLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ev.setSource(0x10000000);
if (Build.VERSION.SDK_INT < 32) {
ev.setSource(0x10000000);
}
boolean parentConsumed = parent.onInterceptTouchEvent(ev);
boolean superConsumed = super.onInterceptTouchEvent(ev);
return parentConsumed && superConsumed;

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2022-3-14.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.mikepenz.iconics.view.IconicsTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/grades_subject_unknown_help"
android:textColor="?android:textColorSecondary" />
</LinearLayout>
</layout>

View File

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

View File

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

View File

@ -859,7 +859,7 @@
<string name="settings_about_licenses_text">Open-source licenses</string>
<string name="settings_about_privacy_policy_text">Privacy policy</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_text">Update</string>
<string name="settings_about_version_text">Version</string>

View File

@ -1549,4 +1549,6 @@
<string name="card_notes_header_title">Najnowsze notatki</string>
<string name="login_summary_account_child">(uczeń)</string>
<string name="login_summary_account_parent">(rodzic)</string>
<string name="grades_subject_unknown">- nieznany przedmiot -</string>
<string name="grades_subject_unknown_help">{cmd-information-outline} Oceny, których przedmiot nie został podany w dzienniku. Może to być na przykład taki, który nie jest prowadzony w tym roku szkolnym.</string>
</resources>

View File

@ -2,17 +2,17 @@
buildscript {
ext {
kotlin_version = '1.5.30'
kotlin_version = '1.6.10'
release = [
versionName: "4.11.3",
versionCode: 4110399
versionName: "4.11.7",
versionCode: 4110799
]
setup = [
compileSdk: 30,
compileSdk: 33,
minSdk : 16,
targetSdk : 30
targetSdk : 33
]
}
@ -21,10 +21,10 @@ buildscript {
mavenCentral()
}
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 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
}
}

View File

@ -1,6 +1,6 @@
#Wed Feb 17 14:04:38 CET 2021
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME