mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-18 16:22:46 +02:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
18c306b9ea | |||
c6be1a7954 | |||
e8e9f04050 | |||
3700a71c39 | |||
60f0628f5e | |||
80dcd9aa69 | |||
91b685576b | |||
2e3e3dcf3c | |||
118f5e1794 | |||
e902352a4b | |||
2f7fcb6dc3 | |||
21ddb9d706 | |||
efa63452e7 | |||
83f84de019 | |||
b9aca981e5 | |||
5913707519 | |||
dd6a2c0979 | |||
9fdee6e0c7 | |||
b31bf5c1ab | |||
cf4906f2f4 | |||
680a5dfea3 | |||
c1062cd7ed | |||
8edc581f0b | |||
ea9d801d08 | |||
8f72e11d0c | |||
452271e8c0 | |||
7b4effe889 | |||
e2bf48d1b6 | |||
c88056ddb9 | |||
96dbb0a057 | |||
288c80ea26 | |||
5a217aca01 | |||
4bed62aa6f | |||
a4d604e146 |
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 }}
|
androidHome: ${{ env.ANDROID_HOME }}
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup JDK 1.8
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '11'
|
||||||
|
cache: 'gradle'
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v2
|
uses: android-actions/setup-android@v2
|
||||||
- name: Clean build artifacts
|
- 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 }}
|
androidHome: ${{ env.ANDROID_HOME }}
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup JDK 1.8
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '11'
|
||||||
|
cache: 'gradle'
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v2
|
uses: android-actions/setup-android@v2
|
||||||
- name: Clean build artifacts
|
- 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 }}
|
androidHome: ${{ env.ANDROID_HOME }}
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup JDK 1.8
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '11'
|
||||||
|
cache: 'gradle'
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v2
|
uses: android-actions/setup-android@v2
|
||||||
- name: Clean build artifacts
|
- 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
|
# End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,java,kotlin
|
||||||
|
|
||||||
signatures/
|
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: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
apply plugin: 'com.google.firebase.crashlytics'
|
apply plugin: 'com.google.firebase.crashlytics'
|
||||||
|
|
||||||
@ -35,6 +36,9 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled = false
|
minifyEnabled = false
|
||||||
|
manifestPlaceholders = [
|
||||||
|
buildTimestamp: 0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled = true
|
minifyEnabled = true
|
||||||
@ -120,25 +124,25 @@ dependencies {
|
|||||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||||
|
|
||||||
// Android Jetpack
|
// Android Jetpack
|
||||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
||||||
implementation "androidx.core:core-ktx:1.3.2"
|
implementation "androidx.core:core-ktx:1.6.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:2.3.4"
|
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.room:room-runtime:2.2.6"
|
implementation "androidx.room:room-runtime:2.3.0"
|
||||||
implementation "androidx.work:work-runtime-ktx:2.5.0"
|
implementation "androidx.work:work-runtime-ktx:2.6.0"
|
||||||
kapt "androidx.room:room-compiler:2.2.6"
|
kapt "androidx.room:room-compiler:2.3.0"
|
||||||
|
|
||||||
// Google design libs
|
// 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"
|
implementation "com.google.android:flexbox:2.0.1"
|
||||||
|
|
||||||
// Play Services/Firebase
|
// Play Services/Firebase
|
||||||
implementation "com.google.android.gms:play-services-wearable:17.0.0"
|
implementation "com.google.android.gms:play-services-wearable:17.1.0"
|
||||||
implementation "com.google.firebase:firebase-core:18.0.2"
|
implementation "com.google.firebase:firebase-core:19.0.1"
|
||||||
implementation "com.google.firebase:firebase-crashlytics:17.4.0"
|
implementation "com.google.firebase:firebase-crashlytics:18.2.1"
|
||||||
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
|
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
|
||||||
|
|
||||||
// OkHttp, Retrofit, Gson, Jsoup
|
// OkHttp, Retrofit, Gson, Jsoup
|
||||||
@ -153,7 +157,7 @@ dependencies {
|
|||||||
|
|
||||||
// Szkolny.eu libraries/forks
|
// Szkolny.eu libraries/forks
|
||||||
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
|
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
|
||||||
implementation "eu.szkolny:agendacalendarview:5431f03098"
|
implementation "eu.szkolny:agendacalendarview:ac0f3dcf42"
|
||||||
implementation "eu.szkolny:cafebar:5bf0c618de"
|
implementation "eu.szkolny:cafebar:5bf0c618de"
|
||||||
implementation "eu.szkolny.fslogin:lib:2.0.0"
|
implementation "eu.szkolny.fslogin:lib:2.0.0"
|
||||||
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
||||||
@ -168,10 +172,10 @@ dependencies {
|
|||||||
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
|
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
|
||||||
|
|
||||||
// Iconics & related
|
// Iconics & related
|
||||||
implementation "com.mikepenz:iconics-core:5.3.0-b01"
|
implementation "com.mikepenz:iconics-core:5.3.1"
|
||||||
implementation "com.mikepenz:iconics-views:5.3.0-b01"
|
implementation "com.mikepenz:iconics-views:5.3.1"
|
||||||
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
|
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
|
||||||
implementation "eu.szkolny:szkolny-font:1.3"
|
implementation "eu.szkolny:szkolny-font:77e33acc2a"
|
||||||
|
|
||||||
// Other dependencies
|
// Other dependencies
|
||||||
implementation "cat.ereza:customactivityoncrash:2.3.0"
|
implementation "cat.ereza:customactivityoncrash:2.3.0"
|
||||||
|
@ -146,6 +146,7 @@
|
|||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:theme="@style/Base.Theme.AppCompat" />
|
android:theme="@style/Base.Theme.AppCompat" />
|
||||||
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
|
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
|
||||||
|
<activity android:name=".ui.modules.settings.contributors.ContributorsActivity" />
|
||||||
|
|
||||||
<!-- _____ _
|
<!-- _____ _
|
||||||
| __ \ (_)
|
| __ \ (_)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<h3>Wersja 4.8.1, 2021-06-06</h3>
|
<h3>Wersja 4.10, 2021-09-22</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Poprawiono funkcje logowania. @BxOxSxS</li>
|
<li>Dodano wyświetlanie informacji o frekwencji w planie lekcji. @Antoni-Czaplicki</li>
|
||||||
<li>MobiDziennik: naprawiono wysyłanie wiadomości (błąd "nie znaleziono wiadomości").</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/*secret password - removed for source code publication*/
|
/*secret password - removed for source code publication*/
|
||||||
static toys AES_IV[16] = {
|
static toys AES_IV[16] = {
|
||||||
0x90, 0x44, 0x08, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0xda, 0x2a, 0x5f, 0xbe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
val profileId
|
val profileId
|
||||||
get() = profile.id
|
get() = profile.id
|
||||||
|
|
||||||
|
var enableChucker = false
|
||||||
var debugMode = false
|
var debugMode = false
|
||||||
var devMode = false
|
var devMode = false
|
||||||
}
|
}
|
||||||
@ -70,6 +71,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
val permissionManager by lazy { PermissionManager(this) }
|
val permissionManager by lazy { PermissionManager(this) }
|
||||||
val attendanceManager by lazy { AttendanceManager(this) }
|
val attendanceManager by lazy { AttendanceManager(this) }
|
||||||
val buildManager by lazy { BuildManager(this) }
|
val buildManager by lazy { BuildManager(this) }
|
||||||
|
val availabilityManager by lazy { AvailabilityManager(this) }
|
||||||
|
|
||||||
val db
|
val db
|
||||||
get() = App.db
|
get() = App.db
|
||||||
@ -115,10 +117,12 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
HyperLog.initialize(this)
|
HyperLog.initialize(this)
|
||||||
HyperLog.setLogLevel(Log.VERBOSE)
|
HyperLog.setLogLevel(Log.VERBOSE)
|
||||||
HyperLog.setLogFormat(DebugLogFormat(this))
|
HyperLog.setLogFormat(DebugLogFormat(this))
|
||||||
|
if (enableChucker) {
|
||||||
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
|
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
|
||||||
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
|
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
|
||||||
builder.addInterceptor(chuckerInterceptor)
|
builder.addInterceptor(chuckerInterceptor)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http = builder.build()
|
http = builder.build()
|
||||||
|
|
||||||
@ -171,7 +175,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
App.config = Config(App.db)
|
App.config = Config(App.db)
|
||||||
App.profile = Profile(0, 0, 0, "")
|
App.profile = Profile(0, 0, 0, "")
|
||||||
debugMode = BuildConfig.DEBUG
|
debugMode = BuildConfig.DEBUG
|
||||||
devMode = config.debugMode || debugMode
|
devMode = config.devMode ?: debugMode
|
||||||
|
enableChucker = config.enableChucker ?: devMode
|
||||||
|
|
||||||
if (!profileLoadById(config.lastProfileId)) {
|
if (!profileLoadById(config.lastProfileId)) {
|
||||||
db.profileDao().firstId?.let { profileLoadById(it) }
|
db.profileDao().firstId?.let { profileLoadById(it) }
|
||||||
|
@ -14,6 +14,7 @@ import android.graphics.Typeface
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.text.*
|
import android.text.*
|
||||||
import android.text.style.CharacterStyle
|
import android.text.style.CharacterStyle
|
||||||
import android.text.style.ForegroundColorSpan
|
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 Short -> putShort(property.first, property.second as Short)
|
||||||
is Double -> putDouble(property.first, property.second as Double)
|
is Double -> putDouble(property.first, property.second as Double)
|
||||||
is Boolean -> putBoolean(property.first, property.second as Boolean)
|
is Boolean -> putBoolean(property.first, property.second as Boolean)
|
||||||
|
is Array<*> -> putParcelableArray(property.first, property.second as Array<out Parcelable>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
|
|||||||
import pl.szczodrzynski.edziennik.utils.*
|
import pl.szczodrzynski.edziennik.utils.*
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.NavTarget
|
import pl.szczodrzynski.edziennik.utils.models.NavTarget
|
||||||
import pl.szczodrzynski.navlib.*
|
import pl.szczodrzynski.navlib.*
|
||||||
@ -634,45 +636,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.profile.registerName?.let { registerName ->
|
val error = withContext(Dispatchers.IO) {
|
||||||
var status = app.config.sync.registerAvailability[registerName]
|
app.availabilityManager.check(app.profile)
|
||||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
|
||||||
val api = SzkolnyApi(app)
|
|
||||||
val result = withContext(Dispatchers.IO) {
|
|
||||||
return@withContext api.runCatching({
|
|
||||||
val availability = getRegisterAvailability()
|
|
||||||
app.config.sync.registerAvailability = availability
|
|
||||||
availability[registerName]
|
|
||||||
}, onError = {
|
|
||||||
if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) {
|
|
||||||
return@withContext false
|
|
||||||
}
|
}
|
||||||
return@withContext it
|
when (error?.type) {
|
||||||
})
|
Type.NOT_AVAILABLE -> {
|
||||||
}
|
|
||||||
|
|
||||||
when (result) {
|
|
||||||
false -> {
|
|
||||||
Toast.makeText(this@MainActivity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
|
|
||||||
return@let
|
|
||||||
}
|
|
||||||
is Throwable -> {
|
|
||||||
errorSnackbar.addError(result.toApiError(TAG)).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
is RegisterAvailabilityStatus -> {
|
|
||||||
status = result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) {
|
|
||||||
swipeRefreshLayout.isRefreshing = false
|
swipeRefreshLayout.isRefreshing = false
|
||||||
loadTarget(DRAWER_ITEM_HOME)
|
loadTarget(DRAWER_ITEM_HOME)
|
||||||
if (status != null)
|
RegisterUnavailableDialog(this, error.status!!)
|
||||||
RegisterUnavailableDialog(this, status)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Type.API_ERROR -> {
|
||||||
|
errorSnackbar.addError(error.apiError!!).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Type.NO_API_ACCESS -> {
|
||||||
|
Toast.makeText(this, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
swipeRefreshLayout.isRefreshing = true
|
swipeRefreshLayout.isRefreshing = true
|
||||||
@ -699,10 +679,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||||
fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) {
|
fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) {
|
||||||
EventBus.getDefault().removeStickyEvent(event)
|
EventBus.getDefault().removeStickyEvent(event)
|
||||||
app.profile.registerName?.let { registerName ->
|
val error = app.availabilityManager.check(app.profile, cacheOnly = true)
|
||||||
event.data[registerName]?.let {
|
if (error != null) {
|
||||||
RegisterUnavailableDialog(this, it)
|
RegisterUnavailableDialog(this, error.status!!)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -12,10 +12,7 @@ import kotlinx.coroutines.launch
|
|||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.BuildConfig
|
import pl.szczodrzynski.edziennik.BuildConfig
|
||||||
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
|
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
|
||||||
import pl.szczodrzynski.edziennik.config.utils.ConfigMigration
|
import pl.szczodrzynski.edziennik.config.utils.*
|
||||||
import pl.szczodrzynski.edziennik.config.utils.get
|
|
||||||
import pl.szczodrzynski.edziennik.config.utils.set
|
|
||||||
import pl.szczodrzynski.edziennik.config.utils.toHashMap
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -75,10 +72,15 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
|||||||
get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
|
get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
|
||||||
set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
|
set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
|
||||||
|
|
||||||
private var mDebugMode: Boolean? = null
|
private var mDevMode: Boolean? = null
|
||||||
var debugMode: Boolean
|
var devMode: Boolean?
|
||||||
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
|
get() { mDevMode = mDevMode ?: values.getBooleanOrNull("debugMode"); return mDevMode }
|
||||||
set(value) { set("debugMode", value); mDebugMode = value }
|
set(value) { set("debugMode", value?.toString()); mDevMode = value }
|
||||||
|
|
||||||
|
private var mEnableChucker: Boolean? = null
|
||||||
|
var enableChucker: Boolean?
|
||||||
|
get() { mEnableChucker = mEnableChucker ?: values.getBooleanOrNull("enableChucker"); return mEnableChucker }
|
||||||
|
set(value) { set("enableChucker", value?.toString()); mEnableChucker = value }
|
||||||
|
|
||||||
private var mDevModePassword: String? = null
|
private var mDevModePassword: String? = null
|
||||||
var devModePassword: String?
|
var devModePassword: String?
|
||||||
@ -120,6 +122,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
|||||||
get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert }
|
get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert }
|
||||||
set(value) { set("apiInvalidCert", value); mApiInvalidCert = value }
|
set(value) { set("apiInvalidCert", value); mApiInvalidCert = value }
|
||||||
|
|
||||||
|
private var mApiAvailabilityCheck: Boolean? = null
|
||||||
|
var apiAvailabilityCheck: Boolean
|
||||||
|
get() { mApiAvailabilityCheck = mApiAvailabilityCheck ?: values.get("apiAvailabilityCheck", true); return mApiAvailabilityCheck ?: true }
|
||||||
|
set(value) { set("apiAvailabilityCheck", value); mApiAvailabilityCheck = value }
|
||||||
|
|
||||||
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||||
init {
|
init {
|
||||||
|
@ -59,6 +59,9 @@ fun HashMap<String, String?>.get(key: String, default: String?): String? {
|
|||||||
fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean {
|
fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean {
|
||||||
return this[key]?.toBoolean() ?: default
|
return this[key]?.toBoolean() ?: default
|
||||||
}
|
}
|
||||||
|
fun HashMap<String, String?>.getBooleanOrNull(key: String): Boolean? {
|
||||||
|
return this[key]?.toBooleanStrictOrNull()
|
||||||
|
}
|
||||||
fun HashMap<String, String?>.get(key: String, default: Int): Int {
|
fun HashMap<String, String?>.get(key: String, default: Int): Int {
|
||||||
return this[key]?.toIntOrNull() ?: default
|
return this[key]?.toIntOrNull() ?: default
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ class ConfigMigration(app: App, config: Config) {
|
|||||||
if (dataVersion < 3) {
|
if (dataVersion < 3) {
|
||||||
update = null
|
update = null
|
||||||
privacyPolicyAccepted = false
|
privacyPolicyAccepted = false
|
||||||
debugMode = false
|
devMode = null
|
||||||
devModePassword = null
|
devModePassword = null
|
||||||
appInstalledTime = 0L
|
appInstalledTime = 0L
|
||||||
appRateSnackbarTime = 0L
|
appRateSnackbarTime = 0L
|
||||||
|
@ -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_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
|
||||||
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
|
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
|
||||||
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
|
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_API_CLIENT_ID_JST_REFRESH = "42"
|
||||||
|
|
||||||
const val LIBRUS_JST_DEMO_CODE = "68656A21"
|
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_CERTIFICATE_GONE = 363
|
||||||
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
|
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
|
||||||
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
|
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_VULCAN_API_DEPRECATED = 390
|
||||||
|
|
||||||
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501
|
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 {
|
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
|
||||||
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
|
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
@ -18,7 +18,6 @@ import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent
|
|||||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
@ -27,6 +26,7 @@ import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
|||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||||
|
|
||||||
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
|
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -90,35 +90,21 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.registerName?.also { registerName ->
|
val error = app.availabilityManager.check(profile)
|
||||||
var status = app.config.sync.registerAvailability[registerName]
|
when (error?.type) {
|
||||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
Type.NOT_AVAILABLE -> {
|
||||||
val api = SzkolnyApi(app)
|
|
||||||
api.runCatching({
|
|
||||||
val availability = getRegisterAvailability()
|
|
||||||
app.config.sync.registerAvailability = availability
|
|
||||||
status = availability[registerName]
|
|
||||||
}, onError = {
|
|
||||||
val apiError = it.toApiError(TAG)
|
|
||||||
if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) {
|
|
||||||
return@also
|
|
||||||
}
|
|
||||||
taskCallback.onError(apiError)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status?.available != true
|
|
||||||
|| status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) {
|
|
||||||
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
||||||
EventBus.getDefault().postSticky(
|
EventBus.getDefault().postSticky(RegisterAvailabilityEvent())
|
||||||
RegisterAvailabilityEvent(app.config.sync.registerAvailability)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
taskCallback.onCompleted()
|
taskCallback.onCompleted()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Type.API_ERROR -> {
|
||||||
|
taskCallback.onError(error.apiError!!)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else -> return@let
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat
|
|||||||
val courseStudentEndpoint: String
|
val courseStudentEndpoint: String
|
||||||
get() = "Course/$studentId/"
|
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 {
|
fun getEventType(longId: String, name: String): EventType {
|
||||||
val id = longId.crc16().toLong()
|
val id = longId.crc16().toLong()
|
||||||
return eventTypes.singleOrNull { it.id == id } ?: run {
|
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)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val subjectName = subjectElement.text().trim()
|
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()
|
val dateString = examElement.child(2).text().trim()
|
||||||
if (dateString.isBlank()) return@forEach
|
if (dateString.isBlank()) return@forEach
|
||||||
|
@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
|||||||
|
|
||||||
val subjectId = subjectElement.id().trim()
|
val subjectId = subjectElement.id().trim()
|
||||||
val subjectName = subjectElement.child(0).text().trim()
|
val subjectName = subjectElement.child(0).text().trim()
|
||||||
val subject = data.getSubject(subjectId, subjectName)
|
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||||
|
|
||||||
val gradeType = when {
|
val gradeType = when {
|
||||||
subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM
|
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)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val subjectName = subjectElement.text()
|
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 lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date)
|
||||||
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime
|
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 {
|
EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach {
|
||||||
val id = it[1].trim()
|
val id = it[1].trim()
|
||||||
val name = it[2].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
|
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
|
||||||
|
|
||||||
import org.jsoup.Jsoup
|
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_SUBJECT_ID
|
||||||
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
|
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
|
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)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEachIndexed
|
?: return@forEachIndexed
|
||||||
val subjectName = subjectElement.text().trim()
|
val subjectName = subjectElement.text().trim()
|
||||||
val subject = data.getSubject(subjectId, subjectName)
|
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||||
|
|
||||||
/* Getting teacher */
|
/* 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_MANUALS = 2100
|
||||||
const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200
|
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_HOMEWORK = 2300 // not used as an endpoint
|
||||||
|
const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400
|
||||||
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
||||||
|
|
||||||
val MobidziennikFeatures = listOf(
|
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.
|
* 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)
|
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||||
MobidziennikWebManuals(data, lastSync, onSuccess)
|
MobidziennikWebManuals(data, lastSync, onSuccess)
|
||||||
}*/
|
}*/
|
||||||
|
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||||
|
MobidziennikWebTimetable(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
else -> onSuccess(endpointId)
|
else -> onSuccess(endpointId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
|
|||||||
//syncWeeks.clear()
|
//syncWeeks.clear()
|
||||||
//syncWeeks += Date.fromY_m_d("2019-12-19")
|
//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))
|
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?
|
val loginShort: String?
|
||||||
get() = studentLogin?.split('@')?.get(0)
|
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 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
|
val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis
|
||||||
else System.currentTimeMillis()
|
else System.currentTimeMillis()
|
||||||
|
@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
|||||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
||||||
|
|
||||||
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
|
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 }
|
val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis }
|
||||||
?: System.currentTimeMillis()
|
?: System.currentTimeMillis()
|
||||||
|
@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
|
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.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) }
|
||||||
json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(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) }
|
val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val endTime = lesson.getString("TimeTo")?.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
|
?: return@forEach
|
||||||
|
|
||||||
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
|
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
|
||||||
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
|
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
|
||||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
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")
|
val classroom = lesson.getString("Room")
|
||||||
|
|
||||||
Lesson(data.profileId, -1).also {
|
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 }
|
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
|
||||||
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
|
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
|
||||||
|
|
||||||
|
private var mSenderAddressHash: String? = null
|
||||||
|
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?
|
val apiUrl: String?
|
||||||
get() {
|
get() {
|
||||||
val url = when (apiToken[symbol]?.substring(0, 3)) {
|
val url = when (apiToken[symbol]?.substring(0, 3)) {
|
||||||
|
@ -38,7 +38,7 @@ class VulcanHebeAttendance(
|
|||||||
lastSync = lastSync
|
lastSync = lastSync
|
||||||
) { list, _ ->
|
) { list, _ ->
|
||||||
list.forEach { attendance ->
|
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 type = attendance.getJsonObject("PresenceType") ?: return@forEach
|
||||||
val baseType = getBaseType(type)
|
val baseType = getBaseType(type)
|
||||||
val typeName = type.getString("Name") ?: return@forEach
|
val typeName = type.getString("Name") ?: return@forEach
|
||||||
|
@ -97,6 +97,10 @@ class VulcanHebeMain(
|
|||||||
val studentSemesterId = period.getInt("Id") ?: return@forEach
|
val studentSemesterId = period.getInt("Id") ?: return@forEach
|
||||||
val studentSemesterNumber = period.getInt("Number") ?: 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 hebeContext = student.getString("Context")
|
||||||
|
|
||||||
val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true)
|
val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true)
|
||||||
@ -143,6 +147,8 @@ class VulcanHebeMain(
|
|||||||
studentData["schoolSymbol"] = schoolSymbol
|
studentData["schoolSymbol"] = schoolSymbol
|
||||||
studentData["schoolShort"] = schoolShort
|
studentData["schoolShort"] = schoolShort
|
||||||
studentData["schoolName"] = schoolCode
|
studentData["schoolName"] = schoolCode
|
||||||
|
studentData["senderAddressName"] = senderAddressName
|
||||||
|
studentData["senderAddressHash"] = senderAddressHash
|
||||||
studentData["hebeContext"] = hebeContext
|
studentData["hebeContext"] = hebeContext
|
||||||
}
|
}
|
||||||
dateSemester1Start?.let {
|
dateSemester1Start?.let {
|
||||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_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_MESSAGES_SEND
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||||
@ -27,6 +28,22 @@ class VulcanHebeSendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
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()
|
val recipientsArray = JsonArray()
|
||||||
recipients.forEach { teacher ->
|
recipients.forEach { teacher ->
|
||||||
recipientsArray += JsonObject(
|
recipientsArray += JsonObject(
|
||||||
@ -40,10 +57,10 @@ class VulcanHebeSendMessage(
|
|||||||
val senderName = (profile?.accountName ?: profile?.studentNameLong)
|
val senderName = (profile?.accountName ?: profile?.studentNameLong)
|
||||||
?.swapFirstLastName() ?: ""
|
?.swapFirstLastName() ?: ""
|
||||||
val sender = JsonObject(
|
val sender = JsonObject(
|
||||||
"Address" to senderName,
|
"Address" to data.senderAddressName,
|
||||||
"LoginId" to data.studentLoginId.toString(),
|
"LoginId" to data.studentLoginId.toString(),
|
||||||
"Initials" to senderName.getNameInitials(),
|
"Initials" to senderName.getNameInitials(),
|
||||||
"AddressHash" to senderName.sha1Hex()
|
"AddressHash" to data.senderAddressHash
|
||||||
)
|
)
|
||||||
|
|
||||||
apiPost(
|
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.DataVulcan
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE
|
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.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
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
|
||||||
@ -47,7 +48,7 @@ class VulcanHebeTimetable(
|
|||||||
?: previousWeekStart
|
?: previousWeekStart
|
||||||
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
|
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
|
||||||
|
|
||||||
val lastSync = null
|
val lastSync = 0L
|
||||||
|
|
||||||
apiGetList(
|
apiGetList(
|
||||||
TAG,
|
TAG,
|
||||||
@ -106,6 +107,8 @@ class VulcanHebeTimetable(
|
|||||||
"Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}"
|
"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.lessonList.addAll(lessonList)
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)
|
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)
|
||||||
|
@ -4,8 +4,4 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.events
|
package pl.szczodrzynski.edziennik.data.api.events
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
class RegisterAvailabilityEvent()
|
||||||
|
|
||||||
data class RegisterAvailabilityEvent(
|
|
||||||
val data: Map< String, RegisterAvailabilityStatus>
|
|
||||||
)
|
|
||||||
|
@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models
|
|||||||
|
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
|
import androidx.core.util.set
|
||||||
import androidx.core.util.size
|
import androidx.core.util.size
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import com.google.gson.JsonObject
|
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) {
|
fun startProgress(stringRes: Int) {
|
||||||
callback.onStartProgress(stringRes)
|
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.SignatureInterceptor
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
|
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
|
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.*
|
||||||
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.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||||
@ -373,6 +370,15 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
throw SzkolnyApiException(null)
|
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)
|
@Throws(Exception::class)
|
||||||
fun getFirebaseToken(registerName: String): String {
|
fun getFirebaseToken(registerName: String): String {
|
||||||
val response = api.firebaseToken(registerName).execute()
|
val response = api.firebaseToken(registerName).execute()
|
||||||
|
@ -27,6 +27,9 @@ interface SzkolnyService {
|
|||||||
@POST("appUser")
|
@POST("appUser")
|
||||||
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
|
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
|
||||||
|
|
||||||
|
@GET("contributors/android")
|
||||||
|
fun contributors(): Call<ApiResponse<ContributorsResponse>>
|
||||||
|
|
||||||
@GET("updates/app")
|
@GET("updates/app")
|
||||||
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
|
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 provideKey(param1: String, param2: Long): ByteArray {*/
|
||||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||||
return "$param1.MTIzNDU2Nzg5MDOlSskDmW===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MDY8+Uq3So===.$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
|
||||||
|
}
|
@ -64,6 +64,8 @@ abstract class AttendanceDao : BaseDao<Attendance, AttendanceFull> {
|
|||||||
getRawNow("$QUERY WHERE notified = 0 $ORDER_BY")
|
getRawNow("$QUERY WHERE notified = 0 $ORDER_BY")
|
||||||
fun getNotNotifiedNow(profileId: Int) =
|
fun getNotNotifiedNow(profileId: Int) =
|
||||||
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY")
|
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY")
|
||||||
|
fun getAllByDateNow(profileId: Int, date: Date) =
|
||||||
|
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceDate = '${date.stringY_m_d}' $ORDER_BY")
|
||||||
|
|
||||||
// GET ONE - NOW
|
// GET ONE - NOW
|
||||||
fun getByIdNow(profileId: Int, id: Long) =
|
fun getByIdNow(profileId: Int, id: Long) =
|
||||||
|
@ -140,7 +140,7 @@ open class Profile(
|
|||||||
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
||||||
LOGIN_TYPE_PODLASIE -> "podlasie"
|
LOGIN_TYPE_PODLASIE -> "podlasie"
|
||||||
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
||||||
else -> null
|
else -> "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getImageDrawable(context: Context): Drawable {
|
override fun getImageDrawable(context: Context): Drawable {
|
||||||
|
@ -60,7 +60,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
) ?: return@launch
|
) ?: return@launch
|
||||||
app.config.sync.registerAvailability = data
|
app.config.sync.registerAvailability = data
|
||||||
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
|
||||||
EventBus.getDefault().postSticky(RegisterAvailabilityEvent(data))
|
EventBus.getDefault().postSticky(RegisterAvailabilityEvent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,6 @@ class RegisterUnavailableDialog(
|
|||||||
init { run {
|
init { run {
|
||||||
if (activity.isFinishing)
|
if (activity.isFinishing)
|
||||||
return@run
|
return@run
|
||||||
if (status.available && status.minVersionCode <= BuildConfig.VERSION_CODE)
|
|
||||||
return@run
|
|
||||||
onShowListener?.invoke(TAG)
|
onShowListener?.invoke(TAG)
|
||||||
app = activity.applicationContext as App
|
app = activity.applicationContext as App
|
||||||
|
|
||||||
|
@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.dialogs.timetable
|
package pl.szczodrzynski.edziennik.ui.dialogs.timetable
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.MeasureSpec
|
import android.view.View.MeasureSpec
|
||||||
@ -373,25 +376,31 @@ class GenerateBlockTimetableDialog(
|
|||||||
|
|
||||||
val today = Date.getToday().stringY_m_d
|
val today = Date.getToday().stringY_m_d
|
||||||
val now = Time.getNow().stringH_M_S
|
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() }
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
|
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 {
|
try {
|
||||||
val fos = FileOutputStream(outputFile)
|
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return@withContext null
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
|
resolver.openOutputStream(uri).use {
|
||||||
fos.close()
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||||
|
}
|
||||||
|
uri
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("SAVE_IMAGE", e.message, e)
|
Log.e("SAVE_IMAGE", e.message, e)
|
||||||
return@withContext null
|
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()
|
progressDialog.dismiss()
|
||||||
|
@ -8,15 +8,20 @@ import android.content.Intent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
|
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
|
||||||
import pl.szczodrzynski.edziennik.onClick
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
@ -24,6 +29,7 @@ import pl.szczodrzynski.edziennik.setText
|
|||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceDetailsDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
||||||
import pl.szczodrzynski.edziennik.utils.BetterLink
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
@ -34,6 +40,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
class LessonDetailsDialog(
|
class LessonDetailsDialog(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val lesson: LessonFull,
|
val lesson: LessonFull,
|
||||||
|
val attendance: AttendanceFull? = null,
|
||||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
) : CoroutineScope {
|
) : CoroutineScope {
|
||||||
@ -52,6 +59,8 @@ class LessonDetailsDialog(
|
|||||||
private lateinit var adapter: EventListAdapter
|
private lateinit var adapter: EventListAdapter
|
||||||
private val manager
|
private val manager
|
||||||
get() = app.timetableManager
|
get() = app.timetableManager
|
||||||
|
private val attendanceManager
|
||||||
|
get() = app.attendanceManager
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (activity.isFinishing)
|
if (activity.isFinishing)
|
||||||
@ -170,6 +179,27 @@ class LessonDetailsDialog(
|
|||||||
b.teamName = lesson.teamName
|
b.teamName = lesson.teamName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.attendanceDivider.isVisible = attendance != null
|
||||||
|
b.attendanceLayout.isVisible = attendance != null
|
||||||
|
if (attendance != null) {
|
||||||
|
b.attendanceView.setAttendance(attendance, app.attendanceManager, bigView = true)
|
||||||
|
b.attendanceType.text = attendance.typeName
|
||||||
|
b.attendanceIcon.isVisible = attendance.let {
|
||||||
|
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
|
||||||
|
val color = attendanceManager.getAttendanceColor(it)
|
||||||
|
b.attendanceIcon.setImageDrawable(
|
||||||
|
IconicsDrawable(activity, icon).apply {
|
||||||
|
colorInt = color
|
||||||
|
sizeDp = 24
|
||||||
|
}
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
b.attendanceDetails.onClick {
|
||||||
|
AttendanceDetailsDialog(activity, attendance, onShowListener, onDismissListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = EventListAdapter(
|
adapter = EventListAdapter(
|
||||||
activity,
|
activity,
|
||||||
showWeekDay = false,
|
showWeekDay = false,
|
||||||
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.*
|
|||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.MainActivity
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
import pl.szczodrzynski.edziennik.R
|
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.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
|
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 {
|
private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch {
|
||||||
if (!isAdded)
|
if (!isAdded)
|
||||||
return@launch
|
return@launch
|
||||||
|
checkEventTypes()
|
||||||
delay(500)
|
delay(500)
|
||||||
|
|
||||||
agendaDefault = AgendaFragmentDefault(activity, app, b)
|
agendaDefault = AgendaFragmentDefault(activity, app, b)
|
||||||
@ -146,6 +160,7 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
|||||||
}}}
|
}}}
|
||||||
|
|
||||||
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
|
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
|
||||||
|
checkEventTypes()
|
||||||
delay(300)
|
delay(300)
|
||||||
|
|
||||||
val dayList = mutableListOf<EventDay>()
|
val dayList = mutableListOf<EventDay>()
|
||||||
|
@ -9,7 +9,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|||||||
|
|
||||||
class FragmentLazyPagerAdapter(
|
class FragmentLazyPagerAdapter(
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
swipeRefreshLayout: SwipeRefreshLayout,
|
swipeRefreshLayout: SwipeRefreshLayout? = null,
|
||||||
val fragments: List<Pair<LazyFragment, CharSequence>>
|
val fragments: List<Pair<LazyFragment, CharSequence>>
|
||||||
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
|
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
|
||||||
override fun getPage(position: Int) = fragments[position].first
|
override fun getPage(position: Int) = fragments[position].first
|
||||||
|
@ -41,6 +41,7 @@ class LabFragment : Fragment(), CoroutineScope {
|
|||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
b = TemplateFragmentBinding.inflate(inflater)
|
b = TemplateFragmentBinding.inflate(inflater)
|
||||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||||
|
b.refreshLayout.isEnabled = false
|
||||||
return b.root
|
return b.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,15 +5,18 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.debug
|
package pl.szczodrzynski.edziennik.ui.modules.debug
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Process
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
|
import pl.szczodrzynski.edziennik.config.Config
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||||
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.profile.ProfileRemoveDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.profile.ProfileRemoveDialog
|
||||||
@ -21,6 +24,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
|||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
import pl.szczodrzynski.fslogin.decode
|
import pl.szczodrzynski.fslogin.decode
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class LabPageFragment : LazyFragment(), CoroutineScope {
|
class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||||
companion object {
|
companion object {
|
||||||
@ -75,12 +79,51 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
|||||||
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.chucker.isChecked = App.enableChucker
|
||||||
|
b.chucker.onChange { _, isChecked ->
|
||||||
|
app.config.enableChucker = isChecked
|
||||||
|
App.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.devMode = false
|
||||||
|
App.devMode = 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 {
|
b.unarchive.onClick {
|
||||||
app.profile.archived = false
|
app.profile.archived = false
|
||||||
app.profile.archiveId = null
|
app.profile.archiveId = null
|
||||||
app.profileSave()
|
app.profileSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.resetCert.onClick {
|
||||||
|
app.config.apiInvalidCert = null
|
||||||
|
}
|
||||||
|
|
||||||
|
b.rebuildConfig.onClick {
|
||||||
|
App.config = Config(App.db)
|
||||||
|
}
|
||||||
|
|
||||||
val profiles = app.db.profileDao().allNow
|
val profiles = app.db.profileDao().allNow
|
||||||
b.profile.clear()
|
b.profile.clear()
|
||||||
b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) }
|
b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) }
|
||||||
|
@ -166,7 +166,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
|
|||||||
json.add("App.profile", app.gson.toJsonTree(app.profile))
|
json.add("App.profile", app.gson.toJsonTree(app.profile))
|
||||||
json.add("App.profile.studentData", app.profile.studentData)
|
json.add("App.profile.studentData", app.profile.studentData)
|
||||||
json.add("App.profile.loginStore", loginStore?.data ?: JsonObject())
|
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.items = LabJsonAdapter.expand(json, 0)
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
@ -163,10 +163,9 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
if (app.profile.archived)
|
if (app.profile.archived)
|
||||||
items.add(0, HomeArchiveCard(101, app, activity, this, app.profile))
|
items.add(0, HomeArchiveCard(101, app, activity, this, app.profile))
|
||||||
|
|
||||||
val status = app.config.sync.registerAvailability[app.profile.registerName]
|
val status = app.availabilityManager.check(app.profile, cacheOnly = true)?.status
|
||||||
val update = app.config.update
|
val update = app.config.update
|
||||||
if (update != null && update.versionCode > BuildConfig.VERSION_CODE
|
if (update != null && update.versionCode > BuildConfig.VERSION_CODE || status?.userMessage != null) {
|
||||||
|| status != null && (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)) {
|
|
||||||
items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile))
|
items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,8 @@ class HomeAvailabilityCard(
|
|||||||
}
|
}
|
||||||
holder.root += b.root
|
holder.root += b.root
|
||||||
|
|
||||||
val status = app.config.sync.registerAvailability[profile.registerName]
|
val error = app.availabilityManager.check(profile, cacheOnly = true)
|
||||||
|
val status = error?.status
|
||||||
val update = app.config.update
|
val update = app.config.update
|
||||||
|
|
||||||
if (update == null && status == null)
|
if (update == null && status == null)
|
||||||
@ -58,7 +59,8 @@ class HomeAvailabilityCard(
|
|||||||
|
|
||||||
var onInfoClick = { _: View -> }
|
var onInfoClick = { _: View -> }
|
||||||
|
|
||||||
if (status != null && !status.available && status.userMessage != null) {
|
// show "register unavailable" only when disabled
|
||||||
|
if (status?.userMessage != null) {
|
||||||
b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
b.homeAvailabilityUpdate.isVisible = false
|
b.homeAvailabilityUpdate.isVisible = false
|
||||||
@ -69,6 +71,7 @@ class HomeAvailabilityCard(
|
|||||||
RegisterUnavailableDialog(activity, status)
|
RegisterUnavailableDialog(activity, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// show "update available" when available OR version too old for the register
|
||||||
else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) {
|
else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) {
|
||||||
b.homeAvailabilityTitle.setText(R.string.home_availability_title)
|
b.homeAvailabilityTitle.setText(R.string.home_availability_title)
|
||||||
b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName)
|
b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName)
|
||||||
@ -78,6 +81,9 @@ class HomeAvailabilityCard(
|
|||||||
UpdateAvailableDialog(activity, update)
|
UpdateAvailableDialog(activity, update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
b.root.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
b.homeAvailabilityUpdate.onClick {
|
b.homeAvailabilityUpdate.onClick {
|
||||||
if (update == null)
|
if (update == null)
|
||||||
|
@ -26,13 +26,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
|
||||||
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
|
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
|
||||||
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
|
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -269,52 +268,23 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkAvailability(loginType: Int): Boolean {
|
private suspend fun checkAvailability(loginType: Int): Boolean {
|
||||||
when (loginType) {
|
val error = withContext(Dispatchers.IO) {
|
||||||
LOGIN_TYPE_LIBRUS -> "librus"
|
app.availabilityManager.check(loginType)
|
||||||
LOGIN_TYPE_VULCAN -> "vulcan"
|
} ?: return true
|
||||||
LOGIN_TYPE_IDZIENNIK -> "idziennik"
|
|
||||||
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
|
||||||
LOGIN_TYPE_PODLASIE -> "podlasie"
|
|
||||||
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
|
||||||
else -> null
|
|
||||||
}?.let { registerName ->
|
|
||||||
var status = app.config.sync.registerAvailability[registerName]
|
|
||||||
if (status == null || status.nextCheckAt < currentTimeUnix()) {
|
|
||||||
val api = SzkolnyApi(app)
|
|
||||||
val result = withContext(Dispatchers.IO) {
|
|
||||||
return@withContext api.runCatching({
|
|
||||||
val availability = getRegisterAvailability()
|
|
||||||
app.config.sync.registerAvailability = availability
|
|
||||||
availability[registerName]
|
|
||||||
}, onError = {
|
|
||||||
if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) {
|
|
||||||
return@withContext false
|
|
||||||
}
|
|
||||||
return@withContext it
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
when (result) {
|
return when (error.type) {
|
||||||
false -> {
|
Type.NOT_AVAILABLE -> {
|
||||||
|
RegisterUnavailableDialog(activity, error.status!!)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Type.API_ERROR -> {
|
||||||
|
activity.errorSnackbar.addError(error.apiError!!).show()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Type.NO_API_ACCESS -> {
|
||||||
Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
|
||||||
return@let
|
true
|
||||||
}
|
|
||||||
is Throwable -> {
|
|
||||||
activity.errorSnackbar.addError(result.toApiError(TAG)).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
is RegisterAvailabilityStatus -> {
|
|
||||||
status = result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) {
|
|
||||||
if (status != null)
|
|
||||||
RegisterUnavailableDialog(activity, status)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,9 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
|||||||
if (credential is LoginInfo.FormField) {
|
if (credential is LoginInfo.FormField) {
|
||||||
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
|
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
|
||||||
b.textLayout.hint = app.getString(credential.name)
|
b.textLayout.hint = app.getString(credential.name)
|
||||||
|
if (credential.isNumber) {
|
||||||
|
b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
if (credential.hideText) {
|
if (credential.hideText) {
|
||||||
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
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
|
ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING to R.string.error_312_reason
|
||||||
),
|
),
|
||||||
isRequired = true,
|
isRequired = true,
|
||||||
|
isNumber = true,
|
||||||
validationRegex = "[0-9]+",
|
validationRegex = "[0-9]+",
|
||||||
caseMode = FormField.CaseMode.LOWER_CASE
|
caseMode = FormField.CaseMode.LOWER_CASE
|
||||||
)
|
)
|
||||||
@ -401,6 +402,7 @@ object LoginInfo {
|
|||||||
val validationRegex: String,
|
val validationRegex: String,
|
||||||
val caseMode: CaseMode = CaseMode.UNCHANGED,
|
val caseMode: CaseMode = CaseMode.UNCHANGED,
|
||||||
val hideText: Boolean = false,
|
val hideText: Boolean = false,
|
||||||
|
val isNumber: Boolean = false,
|
||||||
val stripTextRegex: String? = null
|
val stripTextRegex: String? = null
|
||||||
) : BaseCredential(keyName, name, errorCodes) {
|
) : BaseCredential(keyName, name, errorCodes) {
|
||||||
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
|
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
|
||||||
|
@ -53,7 +53,7 @@ class LoginPrizeFragment : Fragment(), CoroutineScope {
|
|||||||
.setTitle(R.string.are_you_sure)
|
.setTitle(R.string.are_you_sure)
|
||||||
.setMessage(R.string.dev_mode_enable_warning)
|
.setMessage(R.string.dev_mode_enable_warning)
|
||||||
.setPositiveButton(R.string.yes) { _, _ ->
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
app.config.debugMode = true
|
app.config.devMode = true
|
||||||
App.devMode = true
|
App.devMode = true
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(activity)
|
||||||
.setTitle("Restart")
|
.setTitle("Restart")
|
||||||
@ -67,8 +67,8 @@ class LoginPrizeFragment : Fragment(), CoroutineScope {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.no) { _, _ ->
|
.setNegativeButton(R.string.no) { _, _ ->
|
||||||
app.config.debugMode = false
|
app.config.devMode = App.debugMode
|
||||||
App.devMode = false
|
App.devMode = App.debugMode
|
||||||
activity.finish()
|
activity.finish()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
@ -76,7 +76,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
val maxProfileId = max(
|
val maxProfileId = max(
|
||||||
app.db.profileDao().lastId ?: 0,
|
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 loginType = args.getInt("loginType", -1)
|
||||||
val loginMode = args.getInt("loginMode", 0)
|
val loginMode = args.getInt("loginMode", 0)
|
||||||
|
@ -503,10 +503,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
text.toString()
|
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) {
|
if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) {
|
||||||
textHtml = textHtml
|
textHtml = textHtml
|
||||||
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "span")
|
.replace("</p><br>", "</p>")
|
||||||
.replace("</p>", "</span>")
|
|
||||||
.replace("<b>", "<strong>")
|
.replace("<b>", "<strong>")
|
||||||
.replace("</b>", "</strong>")
|
.replace("</b>", "</strong>")
|
||||||
.replace("<i>", "<em>")
|
.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.SettingsCard
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsLicenseActivity
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsLicenseActivity
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.settings.contributors.ContributorsActivity
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -90,6 +91,14 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope
|
|||||||
it.subText = BuildConfig.VERSION_NAME + ", " + BuildConfig.BUILD_TYPE
|
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.createMoreItem(card, items = listOf(
|
||||||
util.createActionItem(
|
util.createActionItem(
|
||||||
text = R.string.settings_about_changelog_text,
|
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,
|
text = R.string.settings_theme_drawer_header_text,
|
||||||
icon = CommunityMaterial.Icon2.cmd_image_outline
|
icon = CommunityMaterial.Icon2.cmd_image_outline
|
||||||
) {
|
) {
|
||||||
if (app.config.ui.appBackground == null) {
|
if (app.config.ui.headerBackground == null) {
|
||||||
setHeaderBackground()
|
setHeaderBackground()
|
||||||
return@createActionItem
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -9,19 +9,24 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
|
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.*
|
||||||
import androidx.core.view.marginTop
|
|
||||||
import androidx.core.view.setPadding
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import com.linkedin.android.tachyon.DayView
|
import com.linkedin.android.tachyon.DayView
|
||||||
import com.linkedin.android.tachyon.DayViewConfig
|
import com.linkedin.android.tachyon.DayViewConfig
|
||||||
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
|
import eu.szkolny.font.SzkolnyFont
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
|
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
|
||||||
@ -61,6 +66,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
|
|
||||||
private val manager
|
private val manager
|
||||||
get() = app.timetableManager
|
get() = app.timetableManager
|
||||||
|
private val attendanceManager
|
||||||
|
get() = app.attendanceManager
|
||||||
|
|
||||||
// find SwipeRefreshLayout in the hierarchy
|
// find SwipeRefreshLayout in the hierarchy
|
||||||
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
|
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
|
||||||
@ -102,14 +109,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
val events = withContext(Dispatchers.Default) {
|
val events = withContext(Dispatchers.Default) {
|
||||||
app.db.eventDao().getAllByDateNow(App.profileId, date)
|
app.db.eventDao().getAllByDateNow(App.profileId, date)
|
||||||
}
|
}
|
||||||
processLessonList(lessons, events)
|
val attendanceList = withContext(Dispatchers.Default) {
|
||||||
|
app.db.attendanceDao().getAllByDateNow(App.profileId, date)
|
||||||
|
}
|
||||||
|
processLessonList(lessons, events, attendanceList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
|
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
|
||||||
// no lessons - timetable not downloaded yet
|
// no lessons - timetable not downloaded yet
|
||||||
if (lessons.isEmpty()) {
|
if (lessons.isEmpty()) {
|
||||||
inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
|
inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
|
||||||
@ -151,7 +161,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.scrollView.isVisible = true
|
b.scrollView.isVisible = true
|
||||||
b.dayFrame.removeView(b.dayView)
|
b.dayFrame.removeView(dayView)
|
||||||
b.dayFrame.addView(dayView, 0)
|
b.dayFrame.addView(dayView, 0)
|
||||||
|
|
||||||
// Inflate a label view for each hour the day view will display
|
// Inflate a label view for each hour the day view will display
|
||||||
@ -172,10 +182,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
|
|
||||||
lessons.forEach { it.showAsUnseen = !it.seen }
|
lessons.forEach { it.showAsUnseen = !it.seen }
|
||||||
|
|
||||||
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events)
|
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events, attendanceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>) {
|
private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
|
||||||
if (!isAdded)
|
if (!isAdded)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -192,6 +202,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||||
|
|
||||||
for (lesson in lessons) {
|
for (lesson in lessons) {
|
||||||
|
val attendance = attendanceList.find { it.startTime == lesson.startTime }
|
||||||
val startTime = lesson.displayStartTime ?: continue
|
val startTime = lesson.displayStartTime ?: continue
|
||||||
val endTime = lesson.displayEndTime ?: continue
|
val endTime = lesson.displayEndTime ?: continue
|
||||||
|
|
||||||
@ -208,11 +219,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
val lb = TimetableLessonBinding.bind(eventView)
|
val lb = TimetableLessonBinding.bind(eventView)
|
||||||
eventViews += eventView
|
eventViews += eventView
|
||||||
|
|
||||||
eventView.tag = lesson
|
eventView.tag = lesson to attendance
|
||||||
|
|
||||||
eventView.setOnClickListener {
|
eventView.setOnClickListener {
|
||||||
if (isAdded && it.tag is LessonFull)
|
if (isAdded && it.tag is Pair<*, *>) {
|
||||||
LessonDetailsDialog(activity, it.tag as LessonFull)
|
val (lessonObj, attendanceObj) = it.tag as Pair<*, *>
|
||||||
|
LessonDetailsDialog(
|
||||||
|
activity = activity,
|
||||||
|
lesson = lessonObj as LessonFull,
|
||||||
|
attendance = attendanceObj as AttendanceFull?
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3)
|
val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3)
|
||||||
@ -276,6 +293,18 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
|
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
|
||||||
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
|
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
|
||||||
|
|
||||||
|
lb.attendanceIcon.isVisible = attendance?.let {
|
||||||
|
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
|
||||||
|
val color = attendanceManager.getAttendanceColor(it)
|
||||||
|
lb.attendanceIcon.setImageDrawable(
|
||||||
|
IconicsDrawable(activity, icon).apply {
|
||||||
|
colorInt = color
|
||||||
|
sizeDp = 24
|
||||||
|
}
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
|
||||||
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
|
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
|
||||||
if (!lesson.seen) {
|
if (!lesson.seen) {
|
||||||
manager.markAsSeen(lesson)
|
manager.markAsSeen(lesson)
|
||||||
@ -283,6 +312,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
|
|
||||||
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
||||||
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
|
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
|
||||||
|
val lessonNumberMargin =
|
||||||
|
if (lb.annotationVisible) (-8).dp
|
||||||
|
else 0
|
||||||
|
lb.lessonNumberText.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||||
|
updateMargins(top = lessonNumberMargin, bottom = lessonNumberMargin)
|
||||||
|
}
|
||||||
|
|
||||||
// The day view needs the event time ranges in the start minute/end minute format,
|
// The day view needs the event time ranges in the start minute/end minute format,
|
||||||
// so calculate those here
|
// so calculate those here
|
||||||
|
@ -120,8 +120,8 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
|
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
|
||||||
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
|
startHour = lessonRanges.map { it.startTime.hour }.minOrNull() ?: DEFAULT_START_HOUR
|
||||||
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
|
endHour = lessonRanges.map { it.endTime.hour }.maxOrNull()?.plus(1) ?: DEFAULT_END_HOUR
|
||||||
}
|
}
|
||||||
deferred.await()
|
deferred.await()
|
||||||
if (!isAdded)
|
if (!isAdded)
|
||||||
|
@ -37,9 +37,7 @@ class AttachmentsView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val storageDir by lazy {
|
private val storageDir by lazy {
|
||||||
val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu")
|
Utils.getStorageDir()
|
||||||
storageDir.mkdirs()
|
|
||||||
storageDir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(arguments: Bundle, owner: Any) {
|
fun init(arguments: Bundle, owner: Any) {
|
||||||
@ -82,6 +80,7 @@ class AttachmentsView @JvmOverloads constructor(
|
|||||||
list.adapter = adapter
|
list.adapter = adapter
|
||||||
list.apply {
|
list.apply {
|
||||||
setHasFixedSize(false)
|
setHasFixedSize(false)
|
||||||
|
isNestedScrollingEnabled = false
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
addItemDecoration(SimpleDividerItemDecoration(context))
|
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||||
}
|
}
|
||||||
|
@ -776,7 +776,8 @@ public class Utils {
|
|||||||
public static File getStorageDir() {
|
public static File getStorageDir() {
|
||||||
if (storageDir != null)
|
if (storageDir != null)
|
||||||
return storageDir;
|
return storageDir;
|
||||||
storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu");
|
storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
storageDir = new File(storageDir, "Szkolny.eu");
|
||||||
storageDir.mkdirs();
|
storageDir.mkdirs();
|
||||||
return storageDir;
|
return storageDir;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.utils.managers
|
package pl.szczodrzynski.edziennik.utils.managers
|
||||||
|
|
||||||
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
|
import eu.szkolny.font.SzkolnyFont
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -63,6 +66,17 @@ class AttendanceManager(val app: App) : CoroutineScope {
|
|||||||
else getAttendanceColor(attendance.baseType)
|
else getAttendanceColor(attendance.baseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAttendanceIcon(attendance: Attendance): IIcon? = when (attendance.baseType) {
|
||||||
|
Attendance.TYPE_PRESENT, Attendance.TYPE_PRESENT_CUSTOM -> CommunityMaterial.Icon.cmd_check
|
||||||
|
Attendance.TYPE_ABSENT -> CommunityMaterial.Icon.cmd_close
|
||||||
|
Attendance.TYPE_ABSENT_EXCUSED -> CommunityMaterial.Icon3.cmd_progress_close
|
||||||
|
Attendance.TYPE_RELEASED -> CommunityMaterial.Icon.cmd_account_arrow_right_outline
|
||||||
|
Attendance.TYPE_BELATED -> CommunityMaterial.Icon.cmd_clock_alert_outline
|
||||||
|
Attendance.TYPE_BELATED_EXCUSED -> CommunityMaterial.Icon.cmd_clock_check_outline
|
||||||
|
Attendance.TYPE_DAY_FREE -> SzkolnyFont.Icon.szf_umbrella_beach_outline
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
/* _ _ _____ _____ _ __ _
|
/* _ _ _____ _____ _ __ _
|
||||||
| | | |_ _| / ____| (_)/ _(_)
|
| | | |_ _| / ____| (_)/ _(_)
|
||||||
| | | | | | | (___ _ __ ___ ___ _| |_ _ ___
|
| | | | | | | (___ _ __ ___ ___ _| |_ _ ___
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-9-18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.managers
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.BuildConfig
|
||||||
|
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.toApiError
|
||||||
|
|
||||||
|
class AvailabilityManager(val app: App) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AvailabilityManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val api = SzkolnyApi(app)
|
||||||
|
|
||||||
|
data class Error(
|
||||||
|
val type: Type,
|
||||||
|
val status: RegisterAvailabilityStatus?,
|
||||||
|
val apiError: ApiError?
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun notAvailable(status: RegisterAvailabilityStatus) =
|
||||||
|
Error(Type.NOT_AVAILABLE, status, null)
|
||||||
|
|
||||||
|
fun apiError(apiError: ApiError) =
|
||||||
|
Error(Type.API_ERROR, null, apiError)
|
||||||
|
|
||||||
|
fun noApiAccess() =
|
||||||
|
Error(Type.NO_API_ACCESS, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
NOT_AVAILABLE,
|
||||||
|
API_ERROR,
|
||||||
|
NO_API_ACCESS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(profile: Profile, cacheOnly: Boolean = false): Error? {
|
||||||
|
return check(profile.registerName, cacheOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(loginType: Int, cacheOnly: Boolean = false): Error? {
|
||||||
|
val registerName = when (loginType) {
|
||||||
|
LOGIN_TYPE_LIBRUS -> "librus"
|
||||||
|
LOGIN_TYPE_VULCAN -> "vulcan"
|
||||||
|
LOGIN_TYPE_IDZIENNIK -> "idziennik"
|
||||||
|
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
|
||||||
|
LOGIN_TYPE_PODLASIE -> "podlasie"
|
||||||
|
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
return check(registerName, cacheOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(registerName: String, cacheOnly: Boolean = false): Error? {
|
||||||
|
if (!app.config.apiAvailabilityCheck)
|
||||||
|
return null
|
||||||
|
val status = app.config.sync.registerAvailability[registerName]
|
||||||
|
if (status != null && status.nextCheckAt > currentTimeUnix()) {
|
||||||
|
return reportStatus(status)
|
||||||
|
}
|
||||||
|
if (cacheOnly) {
|
||||||
|
return reportStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val availability = api.getRegisterAvailability()
|
||||||
|
app.config.sync.registerAvailability = availability
|
||||||
|
reportStatus(availability[registerName])
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
reportApiError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportStatus(status: RegisterAvailabilityStatus?): Error? {
|
||||||
|
if (status == null)
|
||||||
|
return null
|
||||||
|
if (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)
|
||||||
|
return Error.notAvailable(status)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportApiError(throwable: Throwable): Error {
|
||||||
|
val apiError = throwable.toApiError(TAG)
|
||||||
|
if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) {
|
||||||
|
app.config.sync.registerAvailability = mapOf()
|
||||||
|
return Error.noApiAccess()
|
||||||
|
}
|
||||||
|
return Error.apiError(apiError)
|
||||||
|
}
|
||||||
|
}
|
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:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/attendances_no_data"
|
android:text="@string/attendances_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -152,6 +152,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/attendances_no_data"
|
android:text="@string/attendances_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/card_grades_no_data"
|
android:text="@string/card_grades_no_data"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/card_grades_no_data"
|
android:text="@string/card_grades_no_data"
|
||||||
android:textSize="16sp" />
|
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>
|
@ -134,7 +134,8 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/shiftedText"
|
android:id="@+id/shiftedText"
|
||||||
@ -290,6 +291,60 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/attendanceDivider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/divider"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/attendanceLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
|
||||||
|
android:id="@+id/attendanceView"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
tools:background="@drawable/bg_rounded_8dp"
|
||||||
|
tools:backgroundTint="#f44336"
|
||||||
|
tools:gravity="center"
|
||||||
|
tools:text="nb"
|
||||||
|
tools:textSize="22sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/attendanceType"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="nieobecność usprawiedliweniowsza1234324" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/attendanceIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
tools:srcCompat="@sample/check" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/attendanceDetails"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dialog_lesson_attendance_details" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/grades_stats_no_data"
|
android:text="@string/grades_stats_no_data"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/homework_no_data"
|
android:text="@string/homework_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-3.
|
~ Copyright (c) Kuba Szczodrzyński 2020-4-3.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<layout xmlns:tools="http://schemas.android.com/tools"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
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"
|
||||||
tools:ignore="HardcodedText">
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
<data>
|
<data>
|
||||||
<variable name="app" type="pl.szczodrzynski.edziennik.App"/>
|
|
||||||
|
<variable
|
||||||
|
name="app"
|
||||||
|
type="pl.szczodrzynski.edziennik.App" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@ -38,6 +41,12 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/chucker"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Chucker" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/last10unseen"
|
android:id="@+id/last10unseen"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -92,15 +101,46 @@
|
|||||||
|
|
||||||
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
android:id="@+id/profile"
|
android:id="@+id/profile"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
|
||||||
|
|
||||||
<com.google.android.material.checkbox.MaterialCheckBox
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:checked="@={app.config.archiverEnabled}"
|
android:checked="@={app.config.archiverEnabled}"
|
||||||
android:text="Archiver enabled" />
|
android:text="Archiver enabled" />
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="@={app.config.apiAvailabilityCheck}"
|
||||||
|
android:text="Availability check enabled" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/resetCert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Reset API signature"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/disableDebug"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="Disable Dev Mode"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:backgroundTint="@color/windowBackgroundRed" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/rebuildConfig"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="Rebuild App.config"
|
||||||
|
android:textAllCaps="false" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
tools:visibility="gone"/>
|
tools:visibility="gone"/>
|
||||||
|
|
||||||
<ScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/content"
|
android:id="@+id/content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
@ -306,6 +306,6 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/messages_no_data"
|
android:text="@string/messages_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/notifications_no_data"
|
android:text="@string/notifications_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -36,12 +36,6 @@
|
|||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:background="@color/md_red_500"
|
android:background="@color/md_red_500"
|
||||||
tools:layout_marginTop="100dp" />
|
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>
|
</FrameLayout>
|
||||||
</pl.szczodrzynski.edziennik.utils.ListenerScrollView>
|
</pl.szczodrzynski.edziennik.utils.ListenerScrollView>
|
||||||
|
|
||||||
|
@ -48,16 +48,16 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="top"
|
android:layout_gravity="top"
|
||||||
android:paddingHorizontal="8dp"
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:baselineAligned="false">
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingVertical="4dp">
|
||||||
<!--tools:background="@drawable/timetable_subject_color_rounded"-->
|
<!--tools:background="@drawable/timetable_subject_color_rounded"-->
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/subjectName"
|
android:id="@+id/subjectName"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
@ -75,8 +75,8 @@
|
|||||||
android:layout_height="12dp"
|
android:layout_height="12dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:visibility="@{unread ? View.VISIBLE : View.GONE}"
|
android:background="@drawable/unread_red_circle"
|
||||||
android:background="@drawable/unread_red_circle" />
|
android:visibility="@{unread ? View.VISIBLE : View.GONE}" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/attendanceIcon"
|
android:id="@+id/attendanceIcon"
|
||||||
@ -87,29 +87,18 @@
|
|||||||
tools:srcCompat="@sample/check"
|
tools:srcCompat="@sample/check"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView4"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_weight="0"
|
|
||||||
app:srcCompat="@drawable/bg_circle"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView6"
|
android:id="@+id/lessonNumberText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:fontFamily="sans-serif-condensed-light"
|
android:fontFamily="sans-serif-condensed-light"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:layout_marginBottom="-4dp"
|
|
||||||
android:paddingHorizontal="4dp"
|
android:paddingHorizontal="4dp"
|
||||||
android:text="@{Integer.toString(lessonNumber)}"
|
android:text="@{Integer.toString(lessonNumber)}"
|
||||||
android:textSize="28sp"
|
android:textSize="28sp"
|
||||||
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
|
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
|
||||||
tools:text="3" />
|
tools:text="3" />
|
||||||
<!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}"
|
|
||||||
android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"-->
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -149,7 +138,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="end|bottom"
|
android:gravity="end|bottom"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="2dp">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/event3"
|
android:id="@+id/event3"
|
||||||
|
@ -856,7 +856,7 @@
|
|||||||
<string name="settings_about_licenses_text">Open-Source-Lizenzen</string>
|
<string name="settings_about_licenses_text">Open-Source-Lizenzen</string>
|
||||||
<string name="settings_about_privacy_policy_text">Datenschutzrichtlinie</string>
|
<string name="settings_about_privacy_policy_text">Datenschutzrichtlinie</string>
|
||||||
<string name="settings_card_register_title">E-Klassenbuch</string>
|
<string name="settings_card_register_title">E-Klassenbuch</string>
|
||||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Juni 2021</string>
|
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - 2021</string>
|
||||||
<string name="settings_about_update_subtext">Klicken Sie hier, um nach Aktualisierungen zu suchen</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_update_text">Aktualisierung</string>
|
||||||
<string name="settings_about_version_text">Version</string>
|
<string name="settings_about_version_text">Version</string>
|
||||||
@ -1234,4 +1234,6 @@
|
|||||||
<string name="you_are_offline_title">Netzwerkverbindung</string>
|
<string name="you_are_offline_title">Netzwerkverbindung</string>
|
||||||
<string name="login_summary_account_child">(Kind)</string>
|
<string name="login_summary_account_child">(Kind)</string>
|
||||||
<string name="login_summary_account_parent">(Elternteil)</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>
|
</resources>
|
||||||
|
@ -55,4 +55,13 @@
|
|||||||
<item quantity="one">%1$s - %2$d unread</item>
|
<item quantity="one">%1$s - %2$d unread</item>
|
||||||
<item quantity="other">%1$s - %2$d unread</item>
|
<item quantity="other">%1$s - %2$d unread</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<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>
|
</resources>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user