mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-01-18 04:46:44 -06:00
Merge branch 'develop'
This commit is contained in:
commit
d60e622626
8
.github/workflows/build-nightly-apk.yml
vendored
8
.github/workflows/build-nightly-apk.yml
vendored
@ -51,10 +51,12 @@ jobs:
|
||||
androidHome: ${{ env.ANDROID_HOME }}
|
||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||
steps:
|
||||
- name: Setup JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
cache: 'gradle'
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: Clean build artifacts
|
||||
|
8
.github/workflows/build-release-aab-play.yml
vendored
8
.github/workflows/build-release-aab-play.yml
vendored
@ -43,10 +43,12 @@ jobs:
|
||||
androidHome: ${{ env.ANDROID_HOME }}
|
||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||
steps:
|
||||
- name: Setup JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
cache: 'gradle'
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: Clean build artifacts
|
||||
|
8
.github/workflows/build-release-apk.yml
vendored
8
.github/workflows/build-release-apk.yml
vendored
@ -43,10 +43,12 @@ jobs:
|
||||
androidHome: ${{ env.ANDROID_HOME }}
|
||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||
steps:
|
||||
- name: Setup JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
cache: 'gradle'
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: Clean build artifacts
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -265,3 +265,4 @@ fabric.properties
|
||||
# End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,java,kotlin
|
||||
|
||||
signatures/
|
||||
.idea/*.xml
|
||||
|
9
.idea/discord.xml
generated
9
.idea/discord.xml
generated
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
<component name="ProjectNotificationSettings">
|
||||
<option name="askShowProject" value="false" />
|
||||
</component>
|
||||
</project>
|
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
</project>
|
13
.idea/runConfigurations.xml
generated
13
.idea/runConfigurations.xml
generated
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
@ -35,6 +36,9 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled = false
|
||||
manifestPlaceholders = [
|
||||
buildTimestamp: 0
|
||||
]
|
||||
}
|
||||
release {
|
||||
minifyEnabled = true
|
||||
@ -120,25 +124,25 @@ dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
|
||||
// Android Jetpack
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.3.4"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.room:room-runtime:2.2.6"
|
||||
implementation "androidx.work:work-runtime-ktx:2.5.0"
|
||||
kapt "androidx.room:room-compiler:2.2.6"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
||||
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.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"
|
||||
|
||||
// Google design libs
|
||||
implementation "com.google.android.material:material:1.3.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.google.android:flexbox:2.0.1"
|
||||
|
||||
// Play Services/Firebase
|
||||
implementation "com.google.android.gms:play-services-wearable:17.0.0"
|
||||
implementation "com.google.firebase:firebase-core:18.0.2"
|
||||
implementation "com.google.firebase:firebase-crashlytics:17.4.0"
|
||||
implementation "com.google.android.gms:play-services-wearable:17.1.0"
|
||||
implementation "com.google.firebase:firebase-core:19.0.1"
|
||||
implementation "com.google.firebase:firebase-crashlytics:18.2.1"
|
||||
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
|
||||
|
||||
// OkHttp, Retrofit, Gson, Jsoup
|
||||
@ -153,7 +157,7 @@ dependencies {
|
||||
|
||||
// Szkolny.eu libraries/forks
|
||||
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
|
||||
implementation "eu.szkolny:agendacalendarview:5431f03098"
|
||||
implementation "eu.szkolny:agendacalendarview:ac0f3dcf42"
|
||||
implementation "eu.szkolny:cafebar:5bf0c618de"
|
||||
implementation "eu.szkolny.fslogin:lib:2.0.0"
|
||||
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
||||
|
@ -146,6 +146,7 @@
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:theme="@style/Base.Theme.AppCompat" />
|
||||
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
|
||||
<activity android:name=".ui.modules.settings.contributors.ContributorsActivity" />
|
||||
|
||||
<!-- _____ _
|
||||
| __ \ (_)
|
||||
|
@ -1,13 +1,13 @@
|
||||
<h3>Wersja 4.8, 2021-05-26</h3>
|
||||
<h3>Wersja 4.9, 2021-09-11</h3>
|
||||
<ul>
|
||||
<li>Dodano ikony dla powiadomień. @Luncenok</li>
|
||||
<li>Terminarz: opcje konfiguracji, widok kompaktowy, grupowanie wydarzeń, znaczki nieprzeczytanych, nowe ikony i wiele innych usprawnień.</li>
|
||||
<li>Wiadomości: usprawiono wyszukiwanie - zapisywanie szukanego tekstu po wejściu w wiadomość.</li>
|
||||
<li>Wiadomości: dodano opcję konfiguracji podpisu przy wysyłaniu wiadomości.</li>
|
||||
<li>Plan lekcji: dodano znacznik aktualnej pory dnia w planie lekcji.</li>
|
||||
<li>Powiadomienia: dodano szczegółowy opis po rozwinięciu.</li>
|
||||
<li>Wydarzenia: nowy rodzaj "lekcja online".</li>
|
||||
<li>Naprawiono odbieranie nagrody w easter egg'u.</li>
|
||||
<li>Vulcan: naprawiono brakujące lekcje w planie. @Antoni-Czaplicki</li>
|
||||
<li>Vulcan: naprawiono wysyłanie wiadomości. @Antoni-Czaplicki</li>
|
||||
<li>Vulcan: naprawiono brak frekwencji.</li>
|
||||
<li>Naprawiono eksportowanie planu lekcji oraz pobieranie załączników. @doteq</li>
|
||||
<li>Mobidziennik: naprawiono możliwość pobierania przyszłego planu lekcji.</li>
|
||||
<li>Mobidziennik: poprawiono brak nowych linii w wysłanej wiadomości.</li>
|
||||
<li>Dodano ekran "Twórcy aplikacji" w Ustawieniach. @Pengwius</li>
|
||||
<li>Zmieniono domyślne tło nagłówka menu. 😋</li>
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/*secret password - removed for source code publication*/
|
||||
static toys AES_IV[16] = {
|
||||
0x71, 0xcf, 0xdf, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
0x36, 0x60, 0xb0, 0x4b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||
|
||||
|
@ -58,6 +58,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
val profileId
|
||||
get() = profile.id
|
||||
|
||||
var enableChucker = false
|
||||
var debugMode = false
|
||||
var devMode = false
|
||||
}
|
||||
@ -115,9 +116,11 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
HyperLog.initialize(this)
|
||||
HyperLog.setLogLevel(Log.VERBOSE)
|
||||
HyperLog.setLogFormat(DebugLogFormat(this))
|
||||
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
|
||||
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
|
||||
builder.addInterceptor(chuckerInterceptor)
|
||||
if (enableChucker) {
|
||||
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
|
||||
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
|
||||
builder.addInterceptor(chuckerInterceptor)
|
||||
}
|
||||
}
|
||||
|
||||
http = builder.build()
|
||||
@ -172,6 +175,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
App.profile = Profile(0, 0, 0, "")
|
||||
debugMode = BuildConfig.DEBUG
|
||||
devMode = config.debugMode || debugMode
|
||||
enableChucker = config.enableChucker || devMode
|
||||
|
||||
if (!profileLoadById(config.lastProfileId)) {
|
||||
db.profileDao().firstId?.let { profileLoadById(it) }
|
||||
|
@ -14,6 +14,7 @@ import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.*
|
||||
import android.text.style.CharacterStyle
|
||||
import android.text.style.ForegroundColorSpan
|
||||
@ -737,6 +738,7 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
|
||||
is Short -> putShort(property.first, property.second as Short)
|
||||
is Double -> putDouble(property.first, property.second as Double)
|
||||
is Boolean -> putBoolean(property.first, property.second as Boolean)
|
||||
is Array<*> -> putParcelableArray(property.first, property.second as Array<out Parcelable>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
|
||||
set(value) { set("debugMode", value); mDebugMode = value }
|
||||
|
||||
private var mEnableChucker: Boolean? = null
|
||||
var enableChucker: Boolean
|
||||
get() { mEnableChucker = mEnableChucker ?: values.get("enableChucker", false); return mEnableChucker ?: false }
|
||||
set(value) { set("enableChucker", value); mEnableChucker = value }
|
||||
|
||||
private var mDevModePassword: String? = null
|
||||
var devModePassword: String?
|
||||
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }
|
||||
|
@ -24,7 +24,7 @@ const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
|
||||
|
||||
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||
const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc"
|
||||
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
|
||||
const val LIBRUS_REDIRECT_URL = "app://librus"
|
||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
||||
@ -43,7 +43,7 @@ const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
|
||||
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
|
||||
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
|
||||
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
|
||||
const val LIBRUS_API_CLIENT_ID_JST = "49"
|
||||
const val LIBRUS_API_CLIENT_ID_JST = "59"
|
||||
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
|
||||
|
||||
const val LIBRUS_JST_DEMO_CODE = "68656A21"
|
||||
|
@ -195,6 +195,7 @@ const val ERROR_VULCAN_HEBE_FIREBASE_ERROR = 362
|
||||
const val ERROR_VULCAN_HEBE_CERTIFICATE_GONE = 363
|
||||
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
|
||||
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
|
||||
const val ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY = 366
|
||||
const val ERROR_VULCAN_API_DEPRECATED = 390
|
||||
|
||||
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501
|
||||
|
@ -117,6 +117,17 @@ object Regexes {
|
||||
}
|
||||
|
||||
|
||||
val MOBIDZIENNIK_TIMETABLE_TOP by lazy {
|
||||
"""<div class="plansc_top">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_TIMETABLE_CELL by lazy {
|
||||
"""<div class="plansc_cnt_w" style="(.+?)">.+?style="(.+?)".+?title="(.+?)".+?>\s+(.+?)\s+</div>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_TIMETABLE_LEFT by lazy {
|
||||
"""<div class="plansc_godz">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
|
||||
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
|
||||
|
@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat
|
||||
val courseStudentEndpoint: String
|
||||
get() = "Course/$studentId/"
|
||||
|
||||
fun getSubject(longId: String, name: String): Subject {
|
||||
val id = longId.crc32()
|
||||
return subjectList.singleOrNull { it.id == id } ?: run {
|
||||
val subject = Subject(profileId, id, name, name)
|
||||
subjectList.put(id, subject)
|
||||
subject
|
||||
}
|
||||
}
|
||||
|
||||
fun getTeacher(firstName: String, lastName: String, longId: String? = null): Teacher {
|
||||
val name = "$firstName $lastName".fixName()
|
||||
val id = name.crc32()
|
||||
return teacherList.singleOrNull { it.id == id }?.also {
|
||||
if (longId != null && it.loginId == null) it.loginId = longId
|
||||
} ?: run {
|
||||
val teacher = Teacher(profileId, id, firstName, lastName, longId)
|
||||
teacherList.put(id, teacher)
|
||||
teacher
|
||||
}
|
||||
}
|
||||
|
||||
fun getTeacherByFirstLast(nameFirstLast: String, longId: String? = null): Teacher {
|
||||
val nameParts = nameFirstLast.split(" ")
|
||||
return getTeacher(nameParts[0], nameParts[1], longId)
|
||||
}
|
||||
|
||||
fun getTeacherByLastFirst(nameLastFirst: String, longId: String? = null): Teacher {
|
||||
val nameParts = nameLastFirst.split(" ")
|
||||
return getTeacher(nameParts[1], nameParts[0], longId)
|
||||
}
|
||||
|
||||
fun getEventType(longId: String, name: String): EventType {
|
||||
val id = longId.crc16().toLong()
|
||||
return eventTypes.singleOrNull { it.id == id } ?: run {
|
||||
|
@ -40,7 +40,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
|
||||
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||
?: return@forEach
|
||||
val subjectName = subjectElement.text().trim()
|
||||
val subject = data.getSubject(subjectId, subjectName)
|
||||
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||
|
||||
val dateString = examElement.child(2).text().trim()
|
||||
if (dateString.isBlank()) return@forEach
|
||||
|
@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
||||
|
||||
val subjectId = subjectElement.id().trim()
|
||||
val subjectName = subjectElement.child(0).text().trim()
|
||||
val subject = data.getSubject(subjectId, subjectName)
|
||||
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||
|
||||
val gradeType = when {
|
||||
subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM
|
||||
|
@ -41,7 +41,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik,
|
||||
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||
?: return@forEach
|
||||
val subjectName = subjectElement.text()
|
||||
val subject = data.getSubject(subjectId, subjectName)
|
||||
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||
|
||||
val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date)
|
||||
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime
|
||||
|
@ -73,7 +73,7 @@ class EdudziennikWebStart(override val data: DataEdudziennik,
|
||||
EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach {
|
||||
val id = it[1].trim()
|
||||
val name = it[2].trim()
|
||||
data.getSubject(id, name)
|
||||
data.getSubject(id.crc32(), name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
|
||||
|
||||
import org.jsoup.Jsoup
|
||||
import pl.szczodrzynski.edziennik.crc32
|
||||
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID
|
||||
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
|
||||
@ -89,7 +90,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik,
|
||||
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||
?: return@forEachIndexed
|
||||
val subjectName = subjectElement.text().trim()
|
||||
val subject = data.getSubject(subjectId, subjectName)
|
||||
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||
|
||||
/* Getting teacher */
|
||||
|
||||
|
@ -18,6 +18,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050
|
||||
const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100
|
||||
const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200
|
||||
const val ENDPOINT_MOBIDZIENNIK_WEB_HOMEWORK = 2300 // not used as an endpoint
|
||||
const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400
|
||||
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
||||
|
||||
val MobidziennikFeatures = listOf(
|
||||
@ -38,6 +39,12 @@ val MobidziennikFeatures = listOf(
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Timetable - web scraping - does nothing if the API_MAIN timetable is enough.
|
||||
*/
|
||||
Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_TIMETABLE, listOf(
|
||||
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_MOBIDZIENNIK_WEB
|
||||
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)),
|
||||
/**
|
||||
* Agenda - "API" + web scraping.
|
||||
*/
|
||||
|
@ -84,6 +84,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||
MobidziennikWebManuals(data, lastSync, onSuccess)
|
||||
}*/
|
||||
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||
MobidziennikWebTimetable(data, lastSync, onSuccess)
|
||||
}
|
||||
else -> onSuccess(endpointId)
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
|
||||
//syncWeeks.clear()
|
||||
//syncWeeks += Date.fromY_m_d("2019-12-19")
|
||||
|
||||
syncWeeks.minBy { it.value }?.let {
|
||||
syncWeeks.minByOrNull { it.value }?.let {
|
||||
data.toRemove.add(DataRemoveModel.Attendance.from(it))
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-9-8.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import org.jsoup.Jsoup
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.Regexes
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
import kotlin.collections.set
|
||||
import kotlin.text.replace
|
||||
|
||||
class MobidziennikWebTimetable(
|
||||
override val data: DataMobidziennik,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : MobidziennikWeb(data, lastSync) {
|
||||
companion object {
|
||||
private const val TAG = "MobidziennikWebTimetable"
|
||||
}
|
||||
|
||||
private val rangesH = mutableMapOf<ClosedFloatingPointRange<Float>, Date>()
|
||||
private val hoursV = mutableMapOf<Int, Pair<Time, Int?>>()
|
||||
private var startDate: Date
|
||||
|
||||
private fun parseCss(css: String): Map<String, String> {
|
||||
return css.split(";").mapNotNull {
|
||||
val spl = it.split(":")
|
||||
if (spl.size != 2)
|
||||
return@mapNotNull null
|
||||
return@mapNotNull spl[0].trim() to spl[1].trim()
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
private fun getRangeH(h: Float): Date? {
|
||||
return rangesH.entries.firstOrNull {
|
||||
h in it.key
|
||||
}?.value
|
||||
}
|
||||
|
||||
private fun stringToDate(date: String): Date? {
|
||||
val items = date.split(" ")
|
||||
val day = items.getOrNull(0)?.toIntOrNull() ?: return null
|
||||
val year = items.getOrNull(2)?.toIntOrNull() ?: return null
|
||||
val month = when (items.getOrNull(1)) {
|
||||
"stycznia" -> 1
|
||||
"lutego" -> 2
|
||||
"marca" -> 3
|
||||
"kwietnia" -> 4
|
||||
"maja" -> 5
|
||||
"czerwca" -> 6
|
||||
"lipca" -> 7
|
||||
"sierpnia" -> 8
|
||||
"września" -> 9
|
||||
"października" -> 10
|
||||
"listopada" -> 11
|
||||
"grudnia" -> 12
|
||||
else -> return null
|
||||
}
|
||||
return Date(year, month, day)
|
||||
}
|
||||
|
||||
init {
|
||||
val currentWeekStart = Week.getWeekStart()
|
||||
val nextWeekEnd = Week.getWeekEnd().stepForward(0, 0, 7)
|
||||
if (Date.getToday().weekDay > 4) {
|
||||
currentWeekStart.stepForward(0, 0, 7)
|
||||
}
|
||||
startDate = data.arguments?.getString("weekStart")?.let {
|
||||
Date.fromY_m_d(it)
|
||||
} ?: currentWeekStart
|
||||
|
||||
val syncFutureDate = startDate > nextWeekEnd
|
||||
// TODO: 2021-09-09 make DataRemoveModel keep extra lessons
|
||||
val syncExtraLessons = false && System.currentTimeMillis() - (lastSync ?: 0) > 2 * DAY * MS
|
||||
if (!syncFutureDate && !syncExtraLessons) {
|
||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE)
|
||||
}
|
||||
else {
|
||||
val types = when {
|
||||
syncFutureDate -> mutableListOf("podstawowy")//, "pozalekcyjny")
|
||||
syncExtraLessons -> mutableListOf("pozalekcyjny")
|
||||
else -> mutableListOf()
|
||||
}
|
||||
|
||||
syncTypes(types, startDate) {
|
||||
// set as synced now only when not syncing future date
|
||||
// (to avoid waiting 2 days for normal sync after future sync)
|
||||
if (syncExtraLessons)
|
||||
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncTypes(types: MutableList<String>, startDate: Date, onSuccess: () -> Unit) {
|
||||
if (types.isEmpty()) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
val type = types.removeAt(0)
|
||||
webGet(TAG, "/dziennik/planlekcji?typ=$type&tydzien=${startDate.stringY_m_d}") { html ->
|
||||
MobidziennikLuckyNumberExtractor(data, html)
|
||||
readRangesH(html)
|
||||
readRangesV(html)
|
||||
readLessons(html)
|
||||
syncTypes(types, startDate, onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readRangesH(html: String) {
|
||||
val htmlH = Regexes.MOBIDZIENNIK_TIMETABLE_TOP.find(html) ?: return
|
||||
val docH = Jsoup.parse(htmlH.value)
|
||||
|
||||
var posH = 0f
|
||||
for (el in docH.select("div > div")) {
|
||||
val css = parseCss(el.attr("style"))
|
||||
val width = css["width"]
|
||||
?.trimEnd('%')
|
||||
?.toFloatOrNull()
|
||||
?: continue
|
||||
val value = stringToDate(el.attr("title"))
|
||||
?: continue
|
||||
|
||||
val range = posH.rangeTo(posH + width)
|
||||
posH += width
|
||||
|
||||
rangesH[range] = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun readRangesV(html: String) {
|
||||
val htmlV = Regexes.MOBIDZIENNIK_TIMETABLE_LEFT.find(html) ?: return
|
||||
val docV = Jsoup.parse(htmlV.value)
|
||||
|
||||
for (el in docV.select("div > div")) {
|
||||
val css = parseCss(el.attr("style"))
|
||||
val top = css["top"]
|
||||
?.trimEnd('%')
|
||||
?.toFloatOrNull()
|
||||
?: continue
|
||||
val values = el.text().split(" ")
|
||||
|
||||
val time = values.getOrNull(0)?.let {
|
||||
Time.fromH_m(it)
|
||||
} ?: continue
|
||||
val num = values.getOrNull(1)?.toIntOrNull()
|
||||
|
||||
hoursV[(top * 100).toInt()] = time to num
|
||||
}
|
||||
}
|
||||
|
||||
private val whitespaceRegex = "\\s+".toRegex()
|
||||
private val classroomRegex = "\\((.*)\\)".toRegex()
|
||||
private fun cleanup(str: String): List<String> {
|
||||
return str
|
||||
.replace(whitespaceRegex, " ")
|
||||
.replace("\n", "")
|
||||
.replace("<small>", "$")
|
||||
.replace("</small>", "$")
|
||||
.replace("<br />", "\n")
|
||||
.replace("<br/>", "\n")
|
||||
.replace("<br>", "\n")
|
||||
.replace("<br />", "\n")
|
||||
.replace("<br/>", "\n")
|
||||
.replace("<br>", "\n")
|
||||
.replace("<b>", "%")
|
||||
.replace("</b>", "%")
|
||||
.replace("<span>", "")
|
||||
.replace("</span>", "")
|
||||
.split("\n")
|
||||
.map { it.trim() }
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag", "LogNotTimber")
|
||||
private fun readLessons(html: String) {
|
||||
val matches = Regexes.MOBIDZIENNIK_TIMETABLE_CELL.findAll(html)
|
||||
|
||||
val noLessonDays = mutableListOf<Date>()
|
||||
for (i in 0..6) {
|
||||
noLessonDays.add(startDate.clone().stepForward(0, 0, i))
|
||||
}
|
||||
|
||||
for (match in matches) {
|
||||
val css = parseCss("${match[1]};${match[2]}")
|
||||
val left = css["left"]?.trimEnd('%')?.toFloatOrNull() ?: continue
|
||||
val top = css["top"]?.trimEnd('%')?.toFloatOrNull() ?: continue
|
||||
val width = css["width"]?.trimEnd('%')?.toFloatOrNull() ?: continue
|
||||
val height = css["height"]?.trimEnd('%')?.toFloatOrNull() ?: continue
|
||||
|
||||
val posH = left + width / 2f
|
||||
val topInt = (top * 100).toInt()
|
||||
val bottomInt = ((top + height) * 100).toInt()
|
||||
|
||||
val lessonDate = getRangeH(posH) ?: continue
|
||||
val (startTime, lessonNumber) = hoursV[topInt] ?: continue
|
||||
val endTime = hoursV[bottomInt]?.first ?: continue
|
||||
|
||||
noLessonDays.remove(lessonDate)
|
||||
|
||||
var typeName: String? = null
|
||||
var subjectName: String? = null
|
||||
var teacherName: String? = null
|
||||
var classroomName: String? = null
|
||||
var teamName: String? = null
|
||||
val items = (cleanup(match[3]) + cleanup(match[4])).toMutableList()
|
||||
|
||||
var length = 0
|
||||
while (items.isNotEmpty() && length != items.size) {
|
||||
length = items.size
|
||||
val toRemove = mutableListOf<String?>()
|
||||
items.forEachIndexed { i, item ->
|
||||
when {
|
||||
item.isEmpty() ->
|
||||
toRemove.add(item)
|
||||
item.contains(":") && item.contains(" - ") ->
|
||||
toRemove.add(item)
|
||||
|
||||
item.startsWith("%") -> {
|
||||
subjectName = item.trim('%')
|
||||
// I have no idea what's going on here
|
||||
// ok now seriously.. the subject (long or short) item
|
||||
// may NOT be 0th, as the HH:MM - HH:MM item may be before
|
||||
// or even the typeName item. As these are always **before**,
|
||||
// they are removed in previous iterations, so the first not removed
|
||||
// item should be the long/short subjectName needing to be removed now.
|
||||
toRemove.add(items[toRemove.size])
|
||||
// ...and this has to be added later
|
||||
toRemove.add(item)
|
||||
}
|
||||
|
||||
item.startsWith("&") -> {
|
||||
typeName = item.trim('&')
|
||||
toRemove.add(item)
|
||||
}
|
||||
typeName != null && (item.contains(typeName!!) || item.contains("</small>")) -> {
|
||||
toRemove.add(item)
|
||||
}
|
||||
|
||||
item.contains("(") && item.contains(")") -> {
|
||||
classroomName = classroomRegex.find(item)?.get(1)
|
||||
items[i] = item.replace("($classroomName)", "").trim()
|
||||
}
|
||||
classroomName != null && item.contains(classroomName!!) -> {
|
||||
items[i] = item.replace("($classroomName)", "").trim()
|
||||
}
|
||||
|
||||
item.contains("class=\"wyjatek tooltip\"") ->
|
||||
toRemove.add(item)
|
||||
}
|
||||
}
|
||||
items.removeAll(toRemove)
|
||||
}
|
||||
|
||||
if (items.size == 2 && items[0].contains(" - ")) {
|
||||
val parts = items[0].split(" - ")
|
||||
teamName = parts[0]
|
||||
teacherName = parts[1]
|
||||
}
|
||||
else if (items.size == 2 && typeName?.contains("odwołana") == true) {
|
||||
teamName = items[0]
|
||||
}
|
||||
else if (items.size == 4) {
|
||||
teamName = items[0]
|
||||
teacherName = items[1]
|
||||
}
|
||||
|
||||
val type = when (typeName) {
|
||||
"zastępstwo" -> Lesson.TYPE_CHANGE
|
||||
"lekcja odwołana", "odwołana" -> Lesson.TYPE_CANCELLED
|
||||
else -> Lesson.TYPE_NORMAL
|
||||
}
|
||||
val subject = subjectName?.let { data.getSubject(null, it) }
|
||||
val teacher = teacherName?.let { data.getTeacherByLastFirst(it) }
|
||||
val team = teamName?.let { data.getTeam(
|
||||
id = null,
|
||||
name = it,
|
||||
schoolCode = data.loginServerName ?: return@let null,
|
||||
isTeamClass = false
|
||||
) }
|
||||
|
||||
Lesson(data.profileId, -1).also {
|
||||
it.type = type
|
||||
if (type == Lesson.TYPE_CANCELLED) {
|
||||
it.oldDate = lessonDate
|
||||
it.oldLessonNumber = lessonNumber
|
||||
it.oldStartTime = startTime
|
||||
it.oldEndTime = endTime
|
||||
it.oldSubjectId = subject?.id ?: -1
|
||||
it.oldTeamId = team?.id ?: -1
|
||||
}
|
||||
else {
|
||||
it.date = lessonDate
|
||||
it.lessonNumber = lessonNumber
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.subjectId = subject?.id ?: -1
|
||||
it.teacherId = teacher?.id ?: -1
|
||||
it.teamId = team?.id ?: -1
|
||||
it.classroom = classroomName
|
||||
}
|
||||
|
||||
it.id = it.buildId()
|
||||
|
||||
val seen = profile?.empty == false || lessonDate < Date.getToday()
|
||||
|
||||
if (it.type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
it.id,
|
||||
seen,
|
||||
seen
|
||||
)
|
||||
)
|
||||
}
|
||||
data.lessonList += it
|
||||
}
|
||||
}
|
||||
|
||||
for (date in noLessonDays) {
|
||||
data.lessonList += Lesson(data.profileId, date.value.toLong()).also {
|
||||
it.type = Lesson.TYPE_NO_LESSONS
|
||||
it.date = date
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,39 +81,4 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
||||
|
||||
val loginShort: String?
|
||||
get() = studentLogin?.split('@')?.get(0)
|
||||
|
||||
fun getSubject(name: String): Subject {
|
||||
val id = name.crc32()
|
||||
return subjectList.singleOrNull { it.id == id } ?: run {
|
||||
val subject = Subject(profileId, id, name, name)
|
||||
subjectList.put(id, subject)
|
||||
subject
|
||||
}
|
||||
}
|
||||
|
||||
fun getTeacher(firstName: String, lastName: String): Teacher {
|
||||
val name = "$firstName $lastName".fixName()
|
||||
return teacherList.singleOrNull { it.fullName == name } ?: run {
|
||||
val id = name.crc32()
|
||||
val teacher = Teacher(profileId, id, firstName, lastName)
|
||||
teacherList.put(id, teacher)
|
||||
teacher
|
||||
}
|
||||
}
|
||||
|
||||
fun getTeam(name: String? = null): Team {
|
||||
if (name == "cała klasa" || name == null) return teamClass ?: run {
|
||||
val id = className!!.crc32()
|
||||
val teamCode = "$schoolShortName:$className"
|
||||
val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1)
|
||||
teamList.put(id, team)
|
||||
return team
|
||||
} else {
|
||||
val id = name.crc32()
|
||||
val teamCode = "$schoolShortName:$name"
|
||||
val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1)
|
||||
teamList.put(id, team)
|
||||
return team
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List<JsonObject>)
|
||||
}
|
||||
|
||||
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
|
||||
val subject = data.getSubject(subjectName)
|
||||
val subject = data.getSubject(null, subjectName)
|
||||
|
||||
val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis
|
||||
else System.currentTimeMillis()
|
||||
|
@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
||||
|
||||
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
|
||||
val subject = data.getSubject(subjectName)
|
||||
val subject = data.getSubject(null, subjectName)
|
||||
|
||||
val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis }
|
||||
?: System.currentTimeMillis()
|
||||
|
@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie,
|
||||
|
||||
init {
|
||||
apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
|
||||
data.getTeam() // Save the class team when it doesn't exist.
|
||||
// Save the class team when it doesn't exist.
|
||||
data.getTeam(
|
||||
id = null,
|
||||
name = data.className ?: "",
|
||||
schoolCode = data.schoolShortName ?: "",
|
||||
isTeamClass = true
|
||||
)
|
||||
|
||||
json.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) }
|
||||
json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) }
|
||||
|
@ -43,14 +43,21 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List<JsonObject>) {
|
||||
val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
|
||||
?: return@forEach
|
||||
val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach
|
||||
val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) }
|
||||
val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(null, it) }
|
||||
?: return@forEach
|
||||
|
||||
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
|
||||
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
|
||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
||||
|
||||
val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach
|
||||
val team = lesson.getString("Group")?.let {
|
||||
data.getTeam(
|
||||
id = null,
|
||||
name = it,
|
||||
schoolCode = data.schoolShortName ?: "",
|
||||
isTeamClass = it == "cała klasa"
|
||||
)
|
||||
} ?: return@forEach
|
||||
val classroom = lesson.getString("Room")
|
||||
|
||||
Lesson(data.profileId, -1).also {
|
||||
|
@ -222,6 +222,16 @@ 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 mSenderAddressName: String? = null
|
||||
var senderAddressName: String?
|
||||
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName }
|
||||
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value }
|
||||
|
||||
val apiUrl: String?
|
||||
get() {
|
||||
val url = when (apiToken[symbol]?.substring(0, 3)) {
|
||||
|
@ -38,7 +38,7 @@ class VulcanHebeAttendance(
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { attendance ->
|
||||
val id = attendance.getLong("AuxPresenceId") ?: return@forEach
|
||||
val id = attendance.getLong("Id") ?: return@forEach
|
||||
val type = attendance.getJsonObject("PresenceType") ?: return@forEach
|
||||
val baseType = getBaseType(type)
|
||||
val typeName = type.getString("Name") ?: return@forEach
|
||||
|
@ -97,6 +97,10 @@ class VulcanHebeMain(
|
||||
val studentSemesterId = period.getInt("Id") ?: return@forEach
|
||||
val studentSemesterNumber = period.getInt("Number") ?: return@forEach
|
||||
|
||||
val senderEntry = student.getJsonObject("SenderEntry")
|
||||
val senderAddressName = senderEntry.getString("Address")
|
||||
val senderAddressHash = senderEntry.getString("AddressHash")
|
||||
|
||||
val hebeContext = student.getString("Context")
|
||||
|
||||
val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true)
|
||||
@ -143,6 +147,8 @@ class VulcanHebeMain(
|
||||
studentData["schoolSymbol"] = schoolSymbol
|
||||
studentData["schoolShort"] = schoolShort
|
||||
studentData["schoolName"] = schoolCode
|
||||
studentData["senderAddressName"] = senderAddressName
|
||||
studentData["senderAddressHash"] = senderAddressHash
|
||||
studentData["hebeContext"] = hebeContext
|
||||
}
|
||||
dateSemester1Start?.let {
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
import com.google.gson.JsonObject
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
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.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
@ -27,6 +28,22 @@ class VulcanHebeSendMessage(
|
||||
}
|
||||
|
||||
init {
|
||||
if (data.senderAddressName == null || data.senderAddressHash == null) {
|
||||
VulcanHebeMain(data).getStudents(data.profile, null) {
|
||||
if (data.senderAddressName == null || data.senderAddressHash == null) {
|
||||
data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY)
|
||||
}
|
||||
else {
|
||||
sendMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessage() {
|
||||
val recipientsArray = JsonArray()
|
||||
recipients.forEach { teacher ->
|
||||
recipientsArray += JsonObject(
|
||||
@ -40,10 +57,10 @@ class VulcanHebeSendMessage(
|
||||
val senderName = (profile?.accountName ?: profile?.studentNameLong)
|
||||
?.swapFirstLastName() ?: ""
|
||||
val sender = JsonObject(
|
||||
"Address" to senderName,
|
||||
"Address" to data.senderAddressName,
|
||||
"LoginId" to data.studentLoginId.toString(),
|
||||
"Initials" to senderName.getNameInitials(),
|
||||
"AddressHash" to senderName.sha1Hex()
|
||||
"AddressHash" to data.senderAddressHash
|
||||
)
|
||||
|
||||
apiPost(
|
||||
|
@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
|
||||
@ -47,7 +48,7 @@ class VulcanHebeTimetable(
|
||||
?: previousWeekStart
|
||||
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
|
||||
|
||||
val lastSync = null
|
||||
val lastSync = 0L
|
||||
|
||||
apiGetList(
|
||||
TAG,
|
||||
@ -106,6 +107,8 @@ class VulcanHebeTimetable(
|
||||
"Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}"
|
||||
)
|
||||
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(dateFrom, dateTo))
|
||||
|
||||
data.lessonList.addAll(lessonList)
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)
|
||||
|
@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models
|
||||
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.set
|
||||
import androidx.core.util.size
|
||||
import androidx.room.OnConflictStrategy
|
||||
import com.google.gson.JsonObject
|
||||
@ -376,4 +377,108 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
||||
fun startProgress(stringRes: Int) {
|
||||
callback.onStartProgress(stringRes)
|
||||
}
|
||||
|
||||
/* _ _ _ _ _
|
||||
| | | | | (_) |
|
||||
| | | | |_ _| |___
|
||||
| | | | __| | / __|
|
||||
| |__| | |_| | \__ \
|
||||
\____/ \__|_|_|__*/
|
||||
fun getSubject(id: Long?, name: String, shortName: String = name): Subject {
|
||||
var subject = subjectList.singleOrNull { it.id == id }
|
||||
if (subject == null)
|
||||
subject = subjectList.singleOrNull { it.longName == name }
|
||||
if (subject == null)
|
||||
subject = subjectList.singleOrNull { it.shortName == name }
|
||||
|
||||
if (subject == null) {
|
||||
subject = Subject(
|
||||
profileId,
|
||||
id ?: name.crc32(),
|
||||
name,
|
||||
shortName
|
||||
)
|
||||
subjectList[subject.id] = subject
|
||||
}
|
||||
return subject
|
||||
}
|
||||
|
||||
fun getTeam(id: Long?, name: String, schoolCode: String, isTeamClass: Boolean = false): Team {
|
||||
if (isTeamClass && teamClass != null)
|
||||
return teamClass as Team
|
||||
var team = teamList.singleOrNull { it.id == id }
|
||||
|
||||
val namePlain = name.replace(" ", "")
|
||||
if (team == null)
|
||||
team = teamList.singleOrNull { it.name.replace(" ", "") == namePlain }
|
||||
|
||||
if (team == null) {
|
||||
team = Team(
|
||||
profileId,
|
||||
id ?: name.crc32(),
|
||||
name,
|
||||
if (isTeamClass) Team.TYPE_CLASS else Team.TYPE_VIRTUAL,
|
||||
"$schoolCode:$name",
|
||||
-1
|
||||
)
|
||||
teamList[team.id] = team
|
||||
}
|
||||
return team
|
||||
}
|
||||
|
||||
fun getTeacher(firstName: String, lastName: String, loginId: String? = null): Teacher {
|
||||
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
|
||||
return validateTeacher(teacher, firstName, lastName, loginId)
|
||||
}
|
||||
|
||||
fun getTeacher(firstNameChar: Char, lastName: String, loginId: String? = null): Teacher {
|
||||
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
|
||||
return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId)
|
||||
}
|
||||
|
||||
fun getTeacherByLastFirst(nameLastFirst: String, loginId: String? = null): Teacher {
|
||||
val nameParts = nameLastFirst.split(" ")
|
||||
return if (nameParts.size == 1)
|
||||
getTeacher(nameParts[0], "", loginId)
|
||||
else
|
||||
getTeacher(nameParts[1], nameParts[0], loginId)
|
||||
}
|
||||
|
||||
fun getTeacherByFirstLast(nameFirstLast: String, loginId: String? = null): Teacher {
|
||||
val nameParts = nameFirstLast.split(" ")
|
||||
return if (nameParts.size == 1)
|
||||
getTeacher(nameParts[0], "", loginId)
|
||||
else
|
||||
getTeacher(nameParts[0], nameParts[1], loginId)
|
||||
}
|
||||
|
||||
fun getTeacherByFDotLast(nameFDotLast: String, loginId: String? = null): Teacher {
|
||||
val nameParts = nameFDotLast.split(".")
|
||||
return if (nameParts.size == 1)
|
||||
getTeacher(nameParts[0], "", loginId)
|
||||
else
|
||||
getTeacher(nameParts[0][0], nameParts[1], loginId)
|
||||
}
|
||||
|
||||
fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String, loginId: String? = null): Teacher {
|
||||
val nameParts = nameFDotSpaceLast.split(".")
|
||||
return if (nameParts.size == 1)
|
||||
getTeacher(nameParts[0], "", loginId)
|
||||
else
|
||||
getTeacher(nameParts[0][0], nameParts[1], loginId)
|
||||
}
|
||||
|
||||
private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String, loginId: String?): Teacher {
|
||||
val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).apply {
|
||||
id = fullName.crc32()
|
||||
teacherList[id] = this
|
||||
}
|
||||
return obj.also {
|
||||
if (loginId != null && it.loginId != null)
|
||||
it.loginId = loginId
|
||||
if (firstName.length > 1)
|
||||
it.name = firstName
|
||||
it.surname = lastName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.ApiCacheIntercept
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
@ -373,6 +370,15 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
throw SzkolnyApiException(null)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getContributors(): ContributorsResponse {
|
||||
val response = api.contributors().execute()
|
||||
if (response.isSuccessful && response.body() != null) {
|
||||
return parseResponse(response)
|
||||
}
|
||||
throw SzkolnyApiException(null)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getFirebaseToken(registerName: String): String {
|
||||
val response = api.firebaseToken(registerName).execute()
|
||||
|
@ -27,6 +27,9 @@ interface SzkolnyService {
|
||||
@POST("appUser")
|
||||
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
|
||||
|
||||
@GET("contributors/android")
|
||||
fun contributors(): Call<ApiResponse<ContributorsResponse>>
|
||||
|
||||
@GET("updates/app")
|
||||
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
|
||||
|
||||
|
@ -46,6 +46,6 @@ object Signing {
|
||||
|
||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||
return "$param1.MTIzNDU2Nzg5MDZ/2nExVD===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MDkdkClKMQ===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class ContributorsResponse(
|
||||
val contributors: List<Item>,
|
||||
val translators: List<Item>
|
||||
) {
|
||||
|
||||
@Parcelize
|
||||
data class Item(
|
||||
val login: String,
|
||||
val name: String?,
|
||||
val avatarUrl: String,
|
||||
val profileUrl: String,
|
||||
val itemUrl: String,
|
||||
val contributions: Int?
|
||||
) : Parcelable
|
||||
}
|
@ -4,11 +4,14 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.timetable
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.graphics.*
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.View.MeasureSpec
|
||||
@ -373,25 +376,31 @@ class GenerateBlockTimetableDialog(
|
||||
|
||||
val today = Date.getToday().stringY_m_d
|
||||
val now = Time.getNow().stringH_M_S
|
||||
val filename = "plan_lekcji_${app.profile.name}_${today}_${now}.png"
|
||||
val resolver: ContentResolver = activity.applicationContext.contentResolver
|
||||
val values = ContentValues()
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||
|
||||
val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() }
|
||||
val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
values.put(MediaStore.MediaColumns.RELATIVE_PATH, File(Environment.DIRECTORY_PICTURES, "Szkolny.eu").path)
|
||||
} else {
|
||||
val picturesDirectory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Szkolny.eu")
|
||||
picturesDirectory.mkdirs()
|
||||
values.put(MediaStore.MediaColumns.DATA, File(picturesDirectory, filename).path)
|
||||
}
|
||||
|
||||
try {
|
||||
val fos = FileOutputStream(outputFile)
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
|
||||
fos.close()
|
||||
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return@withContext null
|
||||
resolver.openOutputStream(uri).use {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
uri
|
||||
} catch (e: Exception) {
|
||||
Log.e("SAVE_IMAGE", e.message, e)
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile)
|
||||
} else {
|
||||
Uri.parse("file://" + outputFile.absolutePath)
|
||||
}
|
||||
uri
|
||||
}
|
||||
|
||||
progressDialog.dismiss()
|
||||
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
|
||||
@ -136,9 +137,22 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkEventTypes() {
|
||||
withContext(Dispatchers.Default) {
|
||||
val eventTypes = app.db.eventTypeDao().getAllNow(app.profileId).map {
|
||||
it.id
|
||||
}
|
||||
val defaultEventTypes = EventType.getTypeColorMap().keys
|
||||
if (!eventTypes.containsAll(defaultEventTypes)) {
|
||||
app.db.eventTypeDao().addDefaultTypes(activity, app.profileId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch {
|
||||
if (!isAdded)
|
||||
return@launch
|
||||
checkEventTypes()
|
||||
delay(500)
|
||||
|
||||
agendaDefault = AgendaFragmentDefault(activity, app, b)
|
||||
@ -146,6 +160,7 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
||||
}}}
|
||||
|
||||
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
|
||||
checkEventTypes()
|
||||
delay(300)
|
||||
|
||||
val dayList = mutableListOf<EventDay>()
|
||||
|
@ -9,7 +9,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
|
||||
class FragmentLazyPagerAdapter(
|
||||
fragmentManager: FragmentManager,
|
||||
swipeRefreshLayout: SwipeRefreshLayout,
|
||||
swipeRefreshLayout: SwipeRefreshLayout? = null,
|
||||
val fragments: List<Pair<LazyFragment, CharSequence>>
|
||||
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
|
||||
override fun getPage(position: Int) = fragments[position].first
|
||||
|
@ -5,10 +5,12 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -21,6 +23,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||
import pl.szczodrzynski.fslogin.decode
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
companion object {
|
||||
@ -75,6 +78,37 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
||||
}
|
||||
|
||||
b.chucker.isChecked = app.config.enableChucker
|
||||
|
||||
b.chucker.onChange { _, isChecked ->
|
||||
app.config.enableChucker = isChecked
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle("Restart")
|
||||
.setMessage("Wymagany restart aplikacji")
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
Process.killProcess(Process.myPid())
|
||||
Runtime.getRuntime().exit(0)
|
||||
exitProcess(0)
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
b.disableDebug.onClick {
|
||||
app.config.debugMode = false
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle("Restart")
|
||||
.setMessage("Wymagany restart aplikacji")
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
Process.killProcess(Process.myPid())
|
||||
Runtime.getRuntime().exit(0)
|
||||
exitProcess(0)
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
b.unarchive.onClick {
|
||||
app.profile.archived = false
|
||||
app.profile.archiveId = null
|
||||
|
@ -166,7 +166,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
|
||||
json.add("App.profile", app.gson.toJsonTree(app.profile))
|
||||
json.add("App.profile.studentData", app.profile.studentData)
|
||||
json.add("App.profile.loginStore", loginStore?.data ?: JsonObject())
|
||||
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values)))
|
||||
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values.toSortedMap())))
|
||||
}
|
||||
adapter.items = LabJsonAdapter.expand(json, 0)
|
||||
adapter.notifyDataSetChanged()
|
||||
|
@ -85,6 +85,9 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
||||
if (credential is LoginInfo.FormField) {
|
||||
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
|
||||
b.textLayout.hint = app.getString(credential.name)
|
||||
if (credential.isNumber) {
|
||||
b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER
|
||||
}
|
||||
if (credential.hideText) {
|
||||
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
|
@ -179,6 +179,7 @@ object LoginInfo {
|
||||
ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING to R.string.error_312_reason
|
||||
),
|
||||
isRequired = true,
|
||||
isNumber = true,
|
||||
validationRegex = "[0-9]+",
|
||||
caseMode = FormField.CaseMode.LOWER_CASE
|
||||
)
|
||||
@ -401,6 +402,7 @@ object LoginInfo {
|
||||
val validationRegex: String,
|
||||
val caseMode: CaseMode = CaseMode.UNCHANGED,
|
||||
val hideText: Boolean = false,
|
||||
val isNumber: Boolean = false,
|
||||
val stripTextRegex: String? = null
|
||||
) : BaseCredential(keyName, name, errorCodes) {
|
||||
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
|
||||
|
@ -76,7 +76,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
||||
|
||||
val maxProfileId = max(
|
||||
app.db.profileDao().lastId ?: 0,
|
||||
activity.profiles.maxBy { it.profile.id }?.profile?.id ?: 0
|
||||
activity.profiles.maxByOrNull { it.profile.id }?.profile?.id ?: 0
|
||||
)
|
||||
val loginType = args.getInt("loginType", -1)
|
||||
val loginMode = args.getInt("loginMode", 0)
|
||||
|
@ -503,10 +503,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
||||
text.toString()
|
||||
}
|
||||
|
||||
textHtml = textHtml
|
||||
.replace("</b><b>", "")
|
||||
.replace("</i><i>", "")
|
||||
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "p")
|
||||
|
||||
if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) {
|
||||
textHtml = textHtml
|
||||
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "span")
|
||||
.replace("</p>", "</span>")
|
||||
.replace("</p><br>", "</p>")
|
||||
.replace("<b>", "<strong>")
|
||||
.replace("</b>", "</strong>")
|
||||
.replace("<i>", "<em>")
|
||||
|
@ -24,6 +24,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsLicenseActivity
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.contributors.ContributorsActivity
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -90,6 +91,14 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope
|
||||
it.subText = BuildConfig.VERSION_NAME + ", " + BuildConfig.BUILD_TYPE
|
||||
},
|
||||
|
||||
util.createActionItem(
|
||||
text = R.string.settings_about_contributors_text,
|
||||
subText = R.string.settings_about_contributors_subtext,
|
||||
icon = CommunityMaterial.Icon.cmd_account_group_outline
|
||||
) {
|
||||
activity.startActivity(Intent(activity, ContributorsActivity::class.java))
|
||||
},
|
||||
|
||||
util.createMoreItem(card, items = listOf(
|
||||
util.createActionItem(
|
||||
text = R.string.settings_about_changelog_text,
|
||||
|
@ -88,7 +88,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
|
||||
text = R.string.settings_theme_drawer_header_text,
|
||||
icon = CommunityMaterial.Icon2.cmd_image_outline
|
||||
) {
|
||||
if (app.config.ui.appBackground == null) {
|
||||
if (app.config.ui.headerBackground == null) {
|
||||
setHeaderBackground()
|
||||
return@createActionItem
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.Bundle
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
|
||||
import pl.szczodrzynski.edziennik.databinding.ContributorsActivityBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ContributorsActivity : AppCompatActivity(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ContributorsActivity"
|
||||
private var contributors: ContributorsResponse? = null
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var b: ContributorsActivityBinding
|
||||
|
||||
private var job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local/private variables go here
|
||||
private val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
app = application as App
|
||||
b = ContributorsActivityBinding.inflate(layoutInflater)
|
||||
setContentView(b.root)
|
||||
|
||||
b.progressBar.isVisible = true
|
||||
b.tabLayout.isVisible = false
|
||||
b.viewPager.isVisible = false
|
||||
|
||||
launch {
|
||||
contributors = contributors ?: SzkolnyApi(app).runCatching(errorSnackbar) {
|
||||
getContributors()
|
||||
} ?: return@launch
|
||||
|
||||
val pagerAdapter = FragmentLazyPagerAdapter(
|
||||
supportFragmentManager,
|
||||
fragments = listOf(
|
||||
ContributorsFragment().apply {
|
||||
arguments = Bundle(
|
||||
"items" to contributors!!.contributors.toTypedArray(),
|
||||
"quantityPluralRes" to R.plurals.contributions_quantity,
|
||||
)
|
||||
} to getString(R.string.contributors),
|
||||
|
||||
ContributorsFragment().apply {
|
||||
arguments = Bundle(
|
||||
"items" to contributors!!.translators.toTypedArray(),
|
||||
"quantityPluralRes" to R.plurals.translations_quantity,
|
||||
)
|
||||
} to getString(R.string.translators),
|
||||
)
|
||||
)
|
||||
|
||||
b.viewPager.apply {
|
||||
offscreenPageLimit = 1
|
||||
adapter = pagerAdapter
|
||||
b.tabLayout.setupWithViewPager(this)
|
||||
}
|
||||
|
||||
b.progressBar.isVisible = false
|
||||
b.tabLayout.isVisible = true
|
||||
b.viewPager.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-9-7.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
|
||||
import pl.szczodrzynski.edziennik.databinding.ContributorsListItemBinding
|
||||
import pl.szczodrzynski.edziennik.plural
|
||||
import pl.szczodrzynski.edziennik.setText
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class ContributorsAdapter(
|
||||
val activity: AppCompatActivity,
|
||||
val items: List<ContributorsResponse.Item>,
|
||||
@PluralsRes
|
||||
val quantityPluralRes: Int
|
||||
) : RecyclerView.Adapter<ContributorsAdapter.ViewHolder>() {
|
||||
companion object {
|
||||
private const val TAG = "ContributorsAdapter"
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val view = ContributorsListItemBinding.inflate(inflater, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
val b = holder.b
|
||||
|
||||
b.text.text = item.name ?: item.login
|
||||
b.subtext.setText(
|
||||
R.string.contributors_subtext_format,
|
||||
item.login,
|
||||
activity.plural(
|
||||
quantityPluralRes,
|
||||
item.contributions ?: 0
|
||||
)
|
||||
)
|
||||
|
||||
b.image.load(item.avatarUrl) {
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
b.root.setOnClickListener {
|
||||
Utils.openUrl(activity, item.itemUrl)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
class ViewHolder(val b: ContributorsListItemBinding) : RecyclerView.ViewHolder(b.root)
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
|
||||
import pl.szczodrzynski.edziennik.databinding.ContributorsListFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||
|
||||
class ContributorsFragment : LazyFragment() {
|
||||
companion object {
|
||||
private const val TAG = "ContributorsFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: ContributorsActivity
|
||||
private lateinit var b: ContributorsListFragmentBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as ContributorsActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
b = ContributorsListFragmentBinding.inflate(inflater)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onPageCreated(): Boolean {
|
||||
val contributorsArray = requireArguments().getParcelableArray("items") as Array<ContributorsResponse.Item>
|
||||
val contributors = contributorsArray.toList()
|
||||
val quantityPluralRes = requireArguments().getInt("quantityPluralRes")
|
||||
|
||||
val adapter = ContributorsAdapter(activity, contributors, quantityPluralRes)
|
||||
b.list.adapter = adapter
|
||||
b.list.apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||
addOnScrollListener(onScrollListener)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -151,7 +151,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
b.scrollView.isVisible = true
|
||||
b.dayFrame.removeView(b.dayView)
|
||||
b.dayFrame.removeView(dayView)
|
||||
b.dayFrame.addView(dayView, 0)
|
||||
|
||||
// Inflate a label view for each hour the day view will display
|
||||
|
@ -120,8 +120,8 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
|
||||
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
|
||||
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
|
||||
startHour = lessonRanges.map { it.startTime.hour }.minOrNull() ?: DEFAULT_START_HOUR
|
||||
endHour = lessonRanges.map { it.endTime.hour }.maxOrNull()?.plus(1) ?: DEFAULT_END_HOUR
|
||||
}
|
||||
deferred.await()
|
||||
if (!isAdded)
|
||||
|
@ -37,9 +37,7 @@ class AttachmentsView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private val storageDir by lazy {
|
||||
val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu")
|
||||
storageDir.mkdirs()
|
||||
storageDir
|
||||
Utils.getStorageDir()
|
||||
}
|
||||
|
||||
fun init(arguments: Bundle, owner: Any) {
|
||||
@ -82,6 +80,7 @@ class AttachmentsView @JvmOverloads constructor(
|
||||
list.adapter = adapter
|
||||
list.apply {
|
||||
setHasFixedSize(false)
|
||||
isNestedScrollingEnabled = false
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||
}
|
||||
|
@ -776,7 +776,8 @@ public class Utils {
|
||||
public static File getStorageDir() {
|
||||
if (storageDir != null)
|
||||
return storageDir;
|
||||
storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu");
|
||||
storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
storageDir = new File(storageDir, "Szkolny.eu");
|
||||
storageDir.mkdirs();
|
||||
return storageDir;
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 62 KiB |
BIN
app/src/main/res/drawable/header.webp
Normal file
BIN
app/src/main/res/drawable/header.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/attendances_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -152,6 +152,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/attendances_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -22,6 +22,7 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/card_grades_no_data"
|
||||
android:textSize="16sp" />
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/card_grades_no_data"
|
||||
android:textSize="16sp" />
|
||||
|
||||
|
100
app/src/main/res/layout/contributors_activity.xml
Normal file
100
app/src/main/res/layout/contributors_activity.xml
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?actionBarSize">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="?actionBarSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="?actionBarSize"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:layout_collapseParallaxMultiplier="0.7">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:scaleType="center"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
android:src="@mipmap/ic_splash" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="64dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/NavView.TextView.Large"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="64dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/contributors_headline"
|
||||
android:textAppearance="@style/NavView.TextView.Medium" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?android:colorBackground"
|
||||
android:foreground="@color/colorSurface_2dp"
|
||||
android:minHeight="?actionBarSize"
|
||||
android:visibility="gone"
|
||||
app:tabIndicatorColor="?colorPrimary"
|
||||
app:tabMode="auto"
|
||||
app:tabSelectedTextColor="?colorPrimary"
|
||||
app:tabTextColor="?android:textColorPrimary"
|
||||
tools:visibility="visible" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:visibility="visible" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
||||
</layout>
|
10
app/src/main/res/layout/contributors_list_fragment.xml
Normal file
10
app/src/main/res/layout/contributors_list_fragment.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/contributors_list_item" />
|
||||
</layout>
|
49
app/src/main/res/layout/contributors_list_item.xml
Normal file
49
app/src/main/res/layout/contributors_list_item.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_account_circle" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/NavView.TextView.Subtitle"
|
||||
tools:text="der Librüsch" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/NavView.TextView.Helper"
|
||||
tools:text="[at]librüsch - ∞ contributions" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</layout>
|
@ -32,6 +32,7 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/grades_stats_no_data"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
|
@ -29,6 +29,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/grades_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/homework_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -3,7 +3,8 @@
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-3.
|
||||
-->
|
||||
|
||||
<layout xmlns:tools="http://schemas.android.com/tools"
|
||||
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
@ -38,6 +39,12 @@
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
|
||||
|
||||
<Switch
|
||||
android:id="@+id/chucker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Chucker" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/last10unseen"
|
||||
android:layout_width="match_parent"
|
||||
@ -101,6 +108,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={app.config.archiverEnabled}"
|
||||
android:text="Archiver enabled" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/disableDebug"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Disable Dev Mode"
|
||||
android:textAllCaps="false"
|
||||
app:backgroundTint="@color/windowBackgroundRed" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
|
@ -57,7 +57,7 @@
|
||||
android:visibility="visible"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<ScrollView
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
@ -306,6 +306,6 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/messages_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/notifications_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -29,6 +29,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/grades_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -24,6 +24,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/grades_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
|
@ -36,12 +36,6 @@
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:background="@color/md_red_500"
|
||||
tools:layout_marginTop="100dp" />
|
||||
|
||||
<com.linkedin.android.tachyon.DayView
|
||||
android:id="@+id/dayView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
</pl.szczodrzynski.edziennik.utils.ListenerScrollView>
|
||||
|
||||
|
@ -856,7 +856,7 @@
|
||||
<string name="settings_about_licenses_text">Open-Source-Lizenzen</string>
|
||||
<string name="settings_about_privacy_policy_text">Datenschutzrichtlinie</string>
|
||||
<string name="settings_card_register_title">E-Klassenbuch</string>
|
||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Mai 2021</string>
|
||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Juni 2021</string>
|
||||
<string name="settings_about_update_subtext">Klicken Sie hier, um nach Aktualisierungen zu suchen</string>
|
||||
<string name="settings_about_update_text">Aktualisierung</string>
|
||||
<string name="settings_about_version_text">Version</string>
|
||||
@ -1234,4 +1234,6 @@
|
||||
<string name="you_are_offline_title">Netzwerkverbindung</string>
|
||||
<string name="login_summary_account_child">(Kind)</string>
|
||||
<string name="login_summary_account_parent">(Elternteil)</string>
|
||||
<string name="settings_about_contributors_text">Anwendungsentwickler</string>
|
||||
<string name="settings_about_contributors_subtext">Liste der Szkolny-Entwickler</string>
|
||||
</resources>
|
||||
|
@ -55,4 +55,13 @@
|
||||
<item quantity="one">%1$s - %2$d unread</item>
|
||||
<item quantity="other">%1$s - %2$d unread</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
<plurals name="contributions_quantity">
|
||||
<item quantity="one">%d contribution</item>
|
||||
<item quantity="other">%d contributions</item>
|
||||
</plurals>
|
||||
<plurals name="translations_quantity">
|
||||
<item quantity="one">%d translation</item>
|
||||
<item quantity="other">%d translations</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
@ -858,7 +858,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 && Kacper Ziubryniewicz\nSeptember 2018 - May 2021</string>
|
||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - June 2021</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>
|
||||
@ -1237,6 +1237,10 @@
|
||||
<string name="permissions_attachment">In order to download the file, you have to grant file storage permission for the application.\n\nClick OK to grant the permission.</string>
|
||||
<string name="permissions_denied">You denied the required permissions for the application.\n\nIn order to grant the permission, open the Permissions screen for Szkolny.eu in phone settings.\n\nClick OK to open app settings now.</string>
|
||||
<string name="permissions_required">Required permissions</string>
|
||||
<string name="settings_about_contributors_text">App contributors</string>
|
||||
<string name="settings_about_contributors_subtext">List of Szkolny.eu contributors</string>
|
||||
<string name="contributors">Contributors</string>
|
||||
<string name="translators">Translators</string>
|
||||
<string name="settings_register_hide_sticks_from_old">Hide sticks from old</string>
|
||||
<string name="build_official">Official build</string>
|
||||
<string name="build_platform_play">Google Play</string>
|
||||
|
@ -165,6 +165,7 @@
|
||||
<string name="error_363" translatable="false">ERROR_VULCAN_HEBE_CERTIFICATE_GONE</string>
|
||||
<string name="error_364" translatable="false">ERROR_VULCAN_HEBE_SERVER_ERROR</string>
|
||||
<string name="error_365" translatable="false">ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND</string>
|
||||
<string name="error_366" translatable="false">ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY</string>
|
||||
<string name="error_390" translatable="false">ERROR_VULCAN_API_DEPRECATED</string>
|
||||
|
||||
<string name="error_501" translatable="false">ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN</string>
|
||||
@ -363,6 +364,7 @@
|
||||
<string name="error_363_reason">VULCAN®: urządzenie usunięte. Zaloguj się ponownie do dziennika.</string>
|
||||
<string name="error_364_reason">VULCAN®: błąd serwera. Dziennik może być przeciążony.</string>
|
||||
<string name="error_365_reason">VULCAN®: nie znaleziono bytu</string>
|
||||
<string name="error_366_reason">Błąd wysyłania wiadomości - brak informacji o nadawcy.</string>
|
||||
<string name="error_390_reason">W związku z wygaszeniem aplikacji Dzienniczek+ przez firmę Vulcan, należy zalogować się ponownie.\n\nAby móc dalej korzystać z aplikacji Szkolny.eu, otwórz Ustawienia i wybierz opcję Dodaj nowego ucznia.\nNastępnie zaloguj się do dziennika Vulcan zgodnie z instrukcją.\n\nPrzepraszamy za niedogodności.</string>
|
||||
|
||||
<string name="error_501_reason">Błędny email lub hasło</string>
|
||||
|
@ -159,4 +159,15 @@
|
||||
<item quantity="few">%d oceny</item> <!-- 2, 3, 4, 32, 33, 34 -->
|
||||
<item quantity="other">%d ocen</item> <!-- 5, 10, 12, 13, 21, 25 -->
|
||||
</plurals>
|
||||
|
||||
<plurals name="contributions_quantity">
|
||||
<item quantity="one">%d commit</item>
|
||||
<item quantity="few">%d commity</item>
|
||||
<item quantity="other">%d commit\'ów</item>
|
||||
</plurals>
|
||||
<plurals name="translations_quantity">
|
||||
<item quantity="one">%d tłumaczenie</item>
|
||||
<item quantity="few">%d tłumaczenia</item>
|
||||
<item quantity="other">%d tłumaczeń</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
@ -921,7 +921,7 @@
|
||||
<string name="settings_about_licenses_text">Licencje open-source</string>
|
||||
<string name="settings_about_privacy_policy_text">Polityka prywatności</string>
|
||||
<string name="settings_card_register_title">E-dziennik</string>
|
||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - maj 2021</string>
|
||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - czerwiec 2021</string>
|
||||
<string name="settings_about_update_subtext">Kliknij, aby sprawdzić aktualizacje</string>
|
||||
<string name="settings_about_update_text">Aktualizacja</string>
|
||||
<string name="settings_about_version_text">Wersja</string>
|
||||
@ -1391,6 +1391,10 @@
|
||||
<string name="see_also">Zobacz także</string>
|
||||
<string name="settings_about_homepage_text">Wejdź na stronę aplikacji</string>
|
||||
<string name="settings_about_homepage_subtext">Uzyskaj pomoc lub wesprzyj autorów</string>
|
||||
<string name="settings_about_contributors_text">Twórcy aplikacji</string>
|
||||
<string name="settings_about_contributors_subtext">Lista twórców Szkolnego</string>
|
||||
<string name="contributors">Współtwórcy</string>
|
||||
<string name="translators">Tłumacze</string>
|
||||
<string name="settings_about_github_text">Kod źródłowy</string>
|
||||
<string name="settings_about_github_subtext">Pomóż w rozwoju aplikacji na GitHubie</string>
|
||||
<string name="profile_config_name_hint">Nazwa profilu</string>
|
||||
@ -1457,4 +1461,6 @@
|
||||
<string name="notification_grade_long_format">Ocena: %s (waga %s)\nPrzedmiot: %s\nKategoria: %s\nOpis: %s\nNauczyciel: %s</string>
|
||||
<string name="notification_notice_long_format">Rodzaj: %s\nNauczyciel: %s\nTreść: %s</string>
|
||||
<string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string>
|
||||
<string name="contributors_subtext_format" translatable="false">\@%s - %s</string>
|
||||
<string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string>
|
||||
</resources>
|
||||
|
12
build.gradle
12
build.gradle
@ -2,11 +2,11 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.4.31'
|
||||
kotlin_version = '1.5.20'
|
||||
|
||||
release = [
|
||||
versionName: "4.8",
|
||||
versionCode: 4080099
|
||||
versionName: "4.9",
|
||||
versionCode: 4090099
|
||||
]
|
||||
|
||||
setup = [
|
||||
@ -21,10 +21,10 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.2.0-beta06"
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.google.gms:google-services:4.3.5'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Wed Feb 17 14:04:38 CET 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
Loading…
x
Reference in New Issue
Block a user