Compare commits

...

74 Commits
v4.7 ... v4.9

Author SHA1 Message Date
91b685576b [4.9] Update build.gradle, signing and changelog. 2021-09-10 23:54:09 +02:00
2e3e3dcf3c [Lab] Add button to disable devmode and Chucker toggle. (#70)
* Add option to disable/enable chucker and option to disable dev mode from lab page

* Change "chucker" to "enableChucker"

* Update App.kt
2021-09-10 23:47:46 +02:00
118f5e1794 [API/Vulcan] Fix missing attendance. (#72) 2021-09-10 23:45:29 +02:00
e902352a4b Merge pull request #69 from szkolny-eu/hotfix/message-attachments
[Messages] Fix downloading and displaying attachment on API 30+.
2021-09-10 23:44:26 +02:00
2f7fcb6dc3 [API/Mobidziennik] Fix Web timetable scrapper. 2021-09-10 17:41:39 +02:00
21ddb9d706 [Git] Update .gitignore for .idea. 2021-09-10 17:20:12 +02:00
efa63452e7 [App] Fix Apply Changes not working due to manifest changes. 2021-09-10 17:17:31 +02:00
83f84de019 [UI] Fix attachments view cut off on API 30+. 2021-09-10 17:11:21 +02:00
b9aca981e5 [App] Change app-wide storage dir to Download subfolder. 2021-09-10 16:56:52 +02:00
5913707519 [UI] Use number keyboard in the PIN field in Vulcan. (#68) 2021-09-10 06:49:17 +02:00
dd6a2c0979 [API/Mobidziennik] Add Web timetable scrapper. (#66)
* [API] Add utils for getting teacher, subject and team by name.

* [API/Mobidziennik] Add Web timetable scrapper.

* [API/Mobidziennik] Add missing Regexes.
2021-09-09 23:14:24 +02:00
9fdee6e0c7 [UI] Fix restoring header background dialog. (#65) 2021-09-09 23:14:07 +02:00
b31bf5c1ab [API/Vulcan] Fix missing timetable entries. (#67) 2021-09-09 23:13:39 +02:00
cf4906f2f4 [API/Vulcan] Fix sending messages. (#64)
* Fix sending message

* Add checking for address name/hash when sending message.

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2021-09-09 18:52:04 +02:00
680a5dfea3 [Mobidziennik] Fix missing linebreaks when sending messages. (#63) 2021-09-08 22:49:14 +02:00
c1062cd7ed [UI] Update drawer header background. (#62) 2021-09-08 22:49:00 +02:00
8edc581f0b [UI] Fix multiplicated day dialog in Agenda. (#61) 2021-09-08 22:48:48 +02:00
ea9d801d08 [DB] Workaround missing event types after profile archiving. (#60) 2021-09-08 22:48:35 +02:00
8f72e11d0c [UI] Center "no data" text view. (#59) 2021-09-08 22:48:21 +02:00
452271e8c0 [UI] Add list of contributors in Settings. (#15)
* Contributors item in settings

* Move contributors activity to settings package && actualize branch

* Update AndroidManifest.xml

* Getting contributors from github api

* Cleaning code

* Fetching data from szkolny api, displaying content, a lot of changes :D

* Strings

* Remove androidx legacy library

* Revert manifest changes

* Remove logging in SzkolnyApi

* Fix app name spelling

* Revert changes to dimens.xml

* Refactor contributors code

* Revert changes to dimens.xml

Again

* Revert changes to build.gradle

* Revert changes to gradle-wrapper.properties

* Revert changes to gradle.properties

* Make user name nullable

* Add caching, refactor plurals, add progress bar

* Update contributors UI

* Shorten activity name in manifest

* Remove unneeded line break

* Remove fragment_translators.xml

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2021-09-08 19:11:14 +02:00
7b4effe889 [UI] Fix block timetable export. (#57)
* Use MediaStore on SDK level >= 29 for timetable export

* Fix imports

* Use subdirectory in the Photos folder

* Use MediaStore for all API levels

* Remove not-null assertion

* Remove unnecessary outputStream close.

* Use File constructor for directory path

* Use File(File, String) constructor

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2021-09-07 22:11:29 +02:00
e2bf48d1b6 [Actions] Use JDK 11. 2021-09-02 18:27:51 +02:00
c88056ddb9 [Gradle] Update library dependencies. 2021-09-02 17:49:01 +02:00
96dbb0a057 [Gradle] Update Kotlin to v1.5.20. 2021-09-02 17:48:02 +02:00
288c80ea26 [Gradle] Update wrapper and AGP to match AS 2020.3.1. 2021-09-02 17:40:34 +02:00
5a217aca01 [4.8.2] Update build.gradle, signing and changelog. 2021-06-15 18:13:18 +02:00
4bed62aa6f [UI] Fix timetable crash when syncing (#54)
* Fix removeView

* Use removeView() instead of removeAllViews()

* Remove dayView from layout file
2021-06-11 22:06:07 +02:00
a4d604e146 [API/Librus] Update JST Client ID (#53)
@6Arin9
2021-06-11 00:05:54 +02:00
ae4405ef78 [4.8.1] Update build.gradle, signing and changelog. 2021-06-06 16:50:04 +02:00
71ca51e813 [Strings] Update copyright dates. 2021-06-06 16:47:10 +02:00
1bf07d736f [API/Librus] Update client ID (#51)
@BxOxSxS
2021-06-02 22:59:57 +02:00
909899612e Revert "[Actions] Change Firebase token to service account file."
This reverts commit 4184fbb2cd.
2021-05-26 21:51:52 +02:00
4184fbb2cd [Actions] Change Firebase token to service account file. 2021-05-26 20:44:48 +02:00
75010c0771 [4.8] Update build.gradle, signing and changelog. 2021-05-26 20:01:08 +02:00
5562498e84 Merge pull request #48 from szkolny-eu/feature/notification-long-text
[Notifications] Add long description text
2021-05-26 20:00:08 +02:00
c2d0940a80 Merge pull request #46 from szkolny-eu/hotfix/mobidziennik-message-list
[Mobidziennik] Fix sending messages returning a not found error
2021-05-26 19:59:49 +02:00
baa98f25c5 [UI] Fix easter egg prize receiving. 2021-05-26 19:09:10 +02:00
26645ee83c [DB] Add migration 93. 2021-05-26 19:00:24 +02:00
85d74bec1c [UI] Add long text for notification descriptions. 2021-05-26 19:00:09 +02:00
fd0fc652a3 Merge pull request #47 from szkolny-eu/feature/elearning-events
[Agenda] Add e-learning event type.
2021-05-26 18:52:41 +02:00
c85dac2e4d [Strings] Update copyright dates. Fix event mark as done translation. 2021-05-26 18:00:37 +02:00
c855f08f9c [API/Mobidziennik] Update messages search query to contain student name. 2021-05-26 17:46:03 +02:00
a31c68e87a [Agenda] Add e-learning event type and DB migration. 2021-04-19 14:18:28 +02:00
99021f6b3a Merge pull request #39 from szkolny-eu/feature/messages-fixes
[Messages] UI updates and fixes
2021-04-14 22:45:44 +02:00
e2b47db3fd Merge branch 'develop' into feature/messages-fixes 2021-04-14 22:42:54 +02:00
8609956ae7 [UI/Timetable] Add current time marker line. (#30) 2021-04-14 22:41:06 +02:00
e25ca930e0 Merge pull request #28 from szkolny-eu/feature/agenda-updates
[UI] Agenda updates
2021-04-14 22:40:27 +02:00
47ec1899a1 [UI/Messages] Add greeting text configuration dialog. 2021-04-14 21:08:27 +02:00
1e8fb6a9ae [UI/Messages] Restore search string after closing a message. Add message counter. 2021-04-14 20:19:04 +02:00
02eb5b7ee4 [UI/Agenda] Disable not available config options. 2021-04-14 17:03:44 +02:00
776806caef [UI/Agenda] Update DayDialog for showing event group. 2021-04-14 17:03:30 +02:00
755b846b50 [UI/Agenda] Move common code to EventManager. 2021-04-14 16:34:31 +02:00
73f3ba17de Merge branch 'develop' into feature/agenda-updates 2021-04-14 14:57:00 +02:00
07fb1e0e12 [UI] Refactor dropdown inputs code. 2021-04-14 11:59:58 +02:00
297867cbf3 [UI] Add event type colors to type dropdown. 2021-04-14 11:21:45 +02:00
db598af28a [UI] Add legend in event details dialog. 2021-04-14 10:20:00 +02:00
ec765c9070 [UI] Fix updating event dialog when editing or removing. 2021-04-14 10:16:22 +02:00
5eaa754401 [UI] Add icons for done and manual events. 2021-04-13 20:50:51 +02:00
b48b5589f4 [UI] Add eggfall. (#22)
* Add eggfall

* Add egg.png

* Add more eggs

* [Gradle] Update android-snowfall to a fork.

* [Eggfall] Add randomizing egg images.

* [UI] Restore snowfall icon. Add separate eggfall setting.

* [Eggfall] Limit eggfall to near-easter date only.

Co-authored-by: GitHub <noreply@github.com>
Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2021-04-12 17:18:53 +02:00
634ef16bc5 [UI] Add notification icons. (#23)
* Add notification icons

* Change color of icons

* [Notifications] Update icon colors.

* [Notifications] Update lucky number icon.

* Add icons to Notifications List Fragment

* Update notifications_list_item.xml

* Move the gravity to the LinearLayout

* add paddingLeft

* Change IconicsImageView to View.

* Rearrange XML attributes.

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
2021-04-12 17:17:52 +02:00
ccf0bdaf05 [4.7.1] Update build.gradle, signing and changelog. 2021-04-12 14:35:43 +02:00
4647da7803 [IDE] Fix XML tags reordering when formatting. 2021-04-12 14:27:51 +02:00
613f271c4e Merge pull request #29 from szkolny-eu/hotfix/flavor-aware-availability
[App] Make registerAvailability flavor-aware.
2021-04-12 14:25:49 +02:00
8b1529f240 [App] Make registerAvailability flavor-aware. 2021-04-12 13:13:10 +02:00
3eb09033bf [Strings] Update copyright date (#26)
* Update copyright date

* Update copyright in other languages
2021-04-12 12:26:52 +02:00
12619f6bde [Agenda] Update scroll listeners code. 2021-04-12 11:50:54 +02:00
f5ceaa9afe [Agenda] Add unread badges to events and groups. 2021-04-11 22:08:33 +02:00
777ae945e0 [Agenda] Implement grouping events by type. 2021-04-10 22:26:43 +02:00
3eae8fb58b [Agenda] Add config dialog. Add compact mode. 2021-04-10 20:51:29 +02:00
b14ef5cd78 [Agenda] Limit event text to 3 lines max. 2021-04-10 18:49:24 +02:00
98bf4f3bdc [Agenda] Implement updating event list when changed. 2021-04-09 21:52:04 +02:00
2d6cf50ca7 [Agenda] Refactor agenda UI code. 2021-04-09 15:32:44 +02:00
95baf9fb9c [UI/Dialogs] Add context menu for teachers' names. (#21)
* [UI] Rearrange BetterLink code.

* [UI/Dialogs] Add context menu for teachers' names.

* [Proguard] Update rules for BetterLink reflection.

* [Proguard] Fix rules for BetterLink reflection.
2021-04-08 00:39:59 +02:00
dd0972b528 [Actions] Fix publishing in Google Play release workflow. 2021-04-07 18:42:50 +02:00
190 changed files with 4476 additions and 1797 deletions

View File

@ -51,10 +51,12 @@ jobs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 1.8
uses: actions/setup-java@v1
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'zulu'
java-version: '11'
cache: 'gradle'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts

View File

@ -43,10 +43,12 @@ jobs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 1.8
uses: actions/setup-java@v1
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'zulu'
java-version: '11'
cache: 'gradle'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts
@ -113,12 +115,11 @@ jobs:
releaseFile: ${{ needs.sign.outputs.signedReleaseFile }}
releaseName: ${{ steps.changelog.outputs.appVersionName }}
track: ${{ secrets.PLAY_RELEASE_TRACK }}
userFraction: 1.0
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
- name: Upload workflow artifact
uses: actions/upload-artifact@v2
if: true
if: always()
with:
name: ${{ steps.changelog.outputs.appVersionName }}
path: |

View File

@ -43,10 +43,12 @@ jobs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 1.8
uses: actions/setup-java@v1
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'zulu'
java-version: '11'
cache: 'gradle'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts

1
.gitignore vendored
View File

@ -265,3 +265,4 @@ fabric.properties
# End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,java,kotlin
signatures/
.idea/*.xml

View File

@ -15,6 +15,7 @@
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -25,6 +26,7 @@
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -36,6 +38,7 @@
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -46,6 +49,7 @@
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -56,6 +60,7 @@
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -66,6 +71,7 @@
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -76,6 +82,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@ -87,6 +94,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@ -98,6 +106,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>

View File

@ -5,6 +5,7 @@
<w>ciasteczko</w>
<w>csrf</w>
<w>edziennik</w>
<w>elearning</w>
<w>gson</w>
<w>hebe</w>
<w>idziennik</w>

9
.idea/discord.xml generated
View File

@ -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
View File

@ -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>

View File

@ -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>

View File

@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
@ -35,6 +36,9 @@ android {
buildTypes {
debug {
minifyEnabled = false
manifestPlaceholders = [
buildTimestamp: 0
]
}
release {
minifyEnabled = true
@ -120,25 +124,25 @@ dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
// Android Jetpack
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.4"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.room:room-runtime:2.2.6"
implementation "androidx.work:work-runtime-ktx:2.5.0"
kapt "androidx.room:room-compiler:2.2.6"
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.room:room-runtime:2.3.0"
implementation "androidx.work:work-runtime-ktx:2.6.0"
kapt "androidx.room:room-compiler:2.3.0"
// Google design libs
implementation "com.google.android.material:material:1.3.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.android:flexbox:2.0.1"
// Play Services/Firebase
implementation "com.google.android.gms:play-services-wearable:17.0.0"
implementation "com.google.firebase:firebase-core:18.0.2"
implementation "com.google.firebase:firebase-crashlytics:17.4.0"
implementation "com.google.android.gms:play-services-wearable:17.1.0"
implementation "com.google.firebase:firebase-core:19.0.1"
implementation "com.google.firebase:firebase-crashlytics:18.2.1"
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
// OkHttp, Retrofit, Gson, Jsoup
@ -152,7 +156,8 @@ dependencies {
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
// Szkolny.eu libraries/forks
implementation "eu.szkolny:agendacalendarview:1799f8ef47"
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
implementation "eu.szkolny:agendacalendarview:ac0f3dcf42"
implementation "eu.szkolny:cafebar:5bf0c618de"
implementation "eu.szkolny.fslogin:lib:2.0.0"
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
@ -180,7 +185,6 @@ dependencies {
implementation "com.github.bassaer:chatmessageview:2.0.1"
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2"
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
implementation "com.github.jetradarmobile:android-snowfall:1.2.0"
implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31"
implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
implementation "com.hypertrack:hyperlog:0.0.10"

View File

@ -32,8 +32,9 @@
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepclassmembernames class androidx.appcompat.view.menu.MenuItemImpl { private *; }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }

View File

@ -146,6 +146,7 @@
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
<activity android:name=".ui.modules.settings.contributors.ContributorsActivity" />
<!-- _____ _
| __ \ (_)

View File

@ -1,13 +1,13 @@
<h3>Wersja 4.7, 2021-04-07</h3>
<h3>Wersja 4.9, 2021-09-11</h3>
<ul>
<li><u>Szkolny.eu jest teraz open source!</u> Zapraszamy na stronę <a href="https://szkolny.eu/">https://szkolny.eu/</a> po więcej ważnych informacji.</li>
<li>Poprawiono wybieranie obrazków (tła nagłówka, tła aplikacji oraz profilu) z dowolnego źródła.</li>
<li>Ukończono tłumaczenie na język angielski. @MarcinK50</li>
<li>Dodano ekran informacji o kompilacji w Ustawieniach.</li>
<li>Zaktualizowano ekran licencji open source.</li>
<li>Naprawiono zatrzymanie aplikacji na Androidzie 4.4 i starszych.</li>
<li>Naprawiono problemy z połączeniem internetowym na Androidzie 4.4 i starszych.</li>
<li>Zoptymalizowano wielkość aplikacji.</li>
<li>Vulcan: naprawiono brakujące lekcje w planie. @Antoni-Czaplicki</li>
<li>Vulcan: naprawiono wysyłanie wiadomości. @Antoni-Czaplicki</li>
<li>Vulcan: naprawiono brak frekwencji.</li>
<li>Naprawiono eksportowanie planu lekcji oraz pobieranie załączników. @doteq</li>
<li>Mobidziennik: naprawiono możliwość pobierania przyszłego planu lekcji.</li>
<li>Mobidziennik: poprawiono brak nowych linii w wysłanej wiadomości.</li>
<li>Dodano ekran "Twórcy aplikacji" w Ustawieniach. @Pengwius</li>
<li>Zmieniono domyślne tło nagłówka menu. 😋</li>
</ul>
<br>
<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0xda, 0x9f, 0xd4, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0x36, 0x60, 0xb0, 0x4b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -58,6 +58,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
val profileId
get() = profile.id
var enableChucker = false
var debugMode = false
var devMode = false
}
@ -115,9 +116,11 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
if (enableChucker) {
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
}
http = builder.build()
@ -172,6 +175,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
App.profile = Profile(0, 0, 0, "")
debugMode = BuildConfig.DEBUG
devMode = config.debugMode || debugMode
enableChucker = config.enableChucker || devMode
if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) }

View File

@ -14,7 +14,9 @@ import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.text.*
import android.text.style.CharacterStyle
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
@ -552,28 +554,46 @@ fun CharSequence?.asBoldSpannable(): Spannable {
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable {
fun CharSequence.asSpannable(
vararg spans: CharacterStyle,
substring: CharSequence? = null,
ignoreCase: Boolean = false,
ignoreDiacritics: Boolean = false
): Spannable {
val spannable = SpannableString(this)
if (substring == null) {
spans.forEach {
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
else if (substring.isNotEmpty()) {
val string =
if (ignoreDiacritics)
this.cleanDiacritics()
else this
substring?.let { substr ->
val string = if (ignoreDiacritics)
this.cleanDiacritics()
else
this
val search = if (ignoreDiacritics)
substr.cleanDiacritics()
else
substr.toString()
var index = string.indexOf(substring, ignoreCase = ignoreCase)
.takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase)
while (index >= 0) {
spans.forEach {
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
var index = 0
do {
index = string.indexOf(
string = search,
startIndex = index,
ignoreCase = ignoreCase
)
if (index >= 0) {
spans.forEach {
spannable.setSpan(
CharacterStyle.wrap(it),
index,
index + substring.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
index += substring.length.coerceAtLeast(1)
}
index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
.takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
}
} while (index >= 0)
} ?: spans.forEach {
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return spannable
}
@ -718,6 +738,7 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
is Short -> putShort(property.first, property.second as Short)
is Double -> putDouble(property.first, property.second as Double)
is Boolean -> putBoolean(property.first, property.second as Boolean)
is Array<*> -> putParcelableArray(property.first, property.second as Array<out Parcelable>)
}
}
}

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.Observer
import androidx.navigation.NavOptions
import com.danimahardhika.cafebar.CafeBar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jetradarmobile.snowfall.SnowfallView
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
@ -81,12 +82,9 @@ import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.navlib.*
@ -470,9 +468,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// IT'S WINTER MY DUDES
val today = Date.getToday()
if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) {
if ((today.month % 11 == 1) && app.config.ui.snowfall) {
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
}
else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
val eggfall = layoutInflater.inflate(R.layout.eggfall, b.rootFrame, false) as SnowfallView
eggfall.setSnowflakeBitmaps(listOf(
BitmapFactory.decodeResource(resources, R.drawable.egg1),
BitmapFactory.decodeResource(resources, R.drawable.egg2),
BitmapFactory.decodeResource(resources, R.drawable.egg3),
BitmapFactory.decodeResource(resources, R.drawable.egg4),
BitmapFactory.decodeResource(resources, R.drawable.egg5),
BitmapFactory.decodeResource(resources, R.drawable.egg6)
))
b.rootFrame.addView(eggfall)
}
// WHAT'S NEW DIALOG
if (app.config.appVersion < BuildConfig.VERSION_CODE) {

View File

@ -80,6 +80,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
set(value) { set("debugMode", value); mDebugMode = value }
private var mEnableChucker: Boolean? = null
var enableChucker: Boolean
get() { mEnableChucker = mEnableChucker ?: values.get("enableChucker", false); return mEnableChucker ?: false }
set(value) { set("enableChucker", value); mEnableChucker = value }
private var mDevModePassword: String? = null
var devModePassword: String?
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.config
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
@ -123,6 +124,19 @@ class ConfigSync(private val config: Config) {
private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null
var registerAvailability: Map<String, RegisterAvailabilityStatus>
get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson<Map<String, RegisterAvailabilityStatus>>(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type) }; return mRegisterAvailability ?: mapOf() }
set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value }
get() {
val flavor = config.values.get("registerAvailabilityFlavor", null as String?)
if (BuildConfig.FLAVOR != flavor)
return mapOf()
mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it ->
gson.fromJson(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type)
}
return mRegisterAvailability ?: mapOf()
}
set(value) {
config.setMap("registerAvailability", value)
config.set("registerAvailabilityFlavor", BuildConfig.FLAVOR)
mRegisterAvailability = value
}
}

View File

@ -49,6 +49,11 @@ class ConfigUI(private val config: Config) {
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
set(value) { config.set("snowfall", value); mSnowfall = value }
private var mEggfall: Boolean? = null
var eggfall: Boolean
get() { mEggfall = mEggfall ?: config.values.get("eggfall", false); return mEggfall ?: false }
set(value) { config.set("eggfall", value); mEggfall = value }
private var mBottomSheetOpened: Boolean? = null
var bottomSheetOpened: Boolean
get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false }

View File

@ -15,8 +15,58 @@ class ProfileConfigUI(private val config: ProfileConfig) {
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mAgendaCompactMode: Boolean? = null
var agendaCompactMode: Boolean
get() { mAgendaCompactMode = mAgendaCompactMode ?: config.values.get("agendaCompactMode", false); return mAgendaCompactMode ?: false }
set(value) { config.set("agendaCompactMode", value); mAgendaCompactMode = value }
private var mAgendaGroupByType: Boolean? = null
var agendaGroupByType: Boolean
get() { mAgendaGroupByType = mAgendaGroupByType ?: config.values.get("agendaGroupByType", false); return mAgendaGroupByType ?: false }
set(value) { config.set("agendaGroupByType", value); mAgendaGroupByType = value }
private var mAgendaLessonChanges: Boolean? = null
var agendaLessonChanges: Boolean
get() { mAgendaLessonChanges = mAgendaLessonChanges ?: config.values.get("agendaLessonChanges", true); return mAgendaLessonChanges ?: true }
set(value) { config.set("agendaLessonChanges", value); mAgendaLessonChanges = value }
private var mAgendaTeacherAbsence: Boolean? = null
var agendaTeacherAbsence: Boolean
get() { mAgendaTeacherAbsence = mAgendaTeacherAbsence ?: config.values.get("agendaTeacherAbsence", true); return mAgendaTeacherAbsence ?: true }
set(value) { config.set("agendaTeacherAbsence", value); mAgendaTeacherAbsence = value }
private var mAgendaElearningMark: Boolean? = null
var agendaElearningMark: Boolean
get() { mAgendaElearningMark = mAgendaElearningMark ?: config.values.get("agendaElearningMark", false); return mAgendaElearningMark ?: false }
set(value) { config.set("agendaElearningMark", value); mAgendaElearningMark = value }
private var mAgendaElearningGroup: Boolean? = null
var agendaElearningGroup: Boolean
get() { mAgendaElearningGroup = mAgendaElearningGroup ?: config.values.get("agendaElearningGroup", true); return mAgendaElearningGroup ?: true }
set(value) { config.set("agendaElearningGroup", value); mAgendaElearningGroup = value }
private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value }
private var mMessagesGreetingOnCompose: Boolean? = null
var messagesGreetingOnCompose: Boolean
get() { mMessagesGreetingOnCompose = mMessagesGreetingOnCompose ?: config.values.get("messagesGreetingOnCompose", true); return mMessagesGreetingOnCompose ?: true }
set(value) { config.set("messagesGreetingOnCompose", value); mMessagesGreetingOnCompose = value }
private var mMessagesGreetingOnReply: Boolean? = null
var messagesGreetingOnReply: Boolean
get() { mMessagesGreetingOnReply = mMessagesGreetingOnReply ?: config.values.get("messagesGreetingOnReply", true); return mMessagesGreetingOnReply ?: true }
set(value) { config.set("messagesGreetingOnReply", value); mMessagesGreetingOnReply = value }
private var mMessagesGreetingOnForward: Boolean? = null
var messagesGreetingOnForward: Boolean
get() { mMessagesGreetingOnForward = mMessagesGreetingOnForward ?: config.values.get("messagesGreetingOnForward", false); return mMessagesGreetingOnForward ?: false }
set(value) { config.set("messagesGreetingOnForward", value); mMessagesGreetingOnForward = value }
private var mMessagesGreetingText: String? = null
var messagesGreetingText: String?
get() { mMessagesGreetingText = mMessagesGreetingText ?: config.values["messagesGreetingText"]; return mMessagesGreetingText }
set(value) { config.set("messagesGreetingText", value); mMessagesGreetingText = value }
}

View File

@ -24,7 +24,7 @@ const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc"
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
const val LIBRUS_REDIRECT_URL = "app://librus"
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
@ -43,7 +43,7 @@ const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49"
const val LIBRUS_API_CLIENT_ID_JST = "59"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21"

View File

@ -195,6 +195,7 @@ const val ERROR_VULCAN_HEBE_FIREBASE_ERROR = 362
const val ERROR_VULCAN_HEBE_CERTIFICATE_GONE = 363
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
const val ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY = 366
const val ERROR_VULCAN_API_DEPRECATED = 390
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501

View File

@ -117,6 +117,17 @@ object Regexes {
}
val MOBIDZIENNIK_TIMETABLE_TOP by lazy {
"""<div class="plansc_top">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_TIMETABLE_CELL by lazy {
"""<div class="plansc_cnt_w" style="(.+?)">.+?style="(.+?)".+?title="(.+?)".+?>\s+(.+?)\s+</div>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_TIMETABLE_LEFT by lazy {
"""<div class="plansc_godz">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)

View File

@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat
val courseStudentEndpoint: String
get() = "Course/$studentId/"
fun getSubject(longId: String, name: String): Subject {
val id = longId.crc32()
return subjectList.singleOrNull { it.id == id } ?: run {
val subject = Subject(profileId, id, name, name)
subjectList.put(id, subject)
subject
}
}
fun getTeacher(firstName: String, lastName: String, longId: String? = null): Teacher {
val name = "$firstName $lastName".fixName()
val id = name.crc32()
return teacherList.singleOrNull { it.id == id }?.also {
if (longId != null && it.loginId == null) it.loginId = longId
} ?: run {
val teacher = Teacher(profileId, id, firstName, lastName, longId)
teacherList.put(id, teacher)
teacher
}
}
fun getTeacherByFirstLast(nameFirstLast: String, longId: String? = null): Teacher {
val nameParts = nameFirstLast.split(" ")
return getTeacher(nameParts[0], nameParts[1], longId)
}
fun getTeacherByLastFirst(nameLastFirst: String, longId: String? = null): Teacher {
val nameParts = nameLastFirst.split(" ")
return getTeacher(nameParts[1], nameParts[0], longId)
}
fun getEventType(longId: String, name: String): EventType {
val id = longId.crc16().toLong()
return eventTypes.singleOrNull { it.id == id } ?: run {

View File

@ -40,7 +40,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
?: return@forEach
val subjectName = subjectElement.text().trim()
val subject = data.getSubject(subjectId, subjectName)
val subject = data.getSubject(subjectId.crc32(), subjectName)
val dateString = examElement.child(2).text().trim()
if (dateString.isBlank()) return@forEach

View File

@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
val subjectId = subjectElement.id().trim()
val subjectName = subjectElement.child(0).text().trim()
val subject = data.getSubject(subjectId, subjectName)
val subject = data.getSubject(subjectId.crc32(), subjectName)
val gradeType = when {
subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM

View File

@ -41,7 +41,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik,
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
?: return@forEach
val subjectName = subjectElement.text()
val subject = data.getSubject(subjectId, subjectName)
val subject = data.getSubject(subjectId.crc32(), subjectName)
val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date)
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime

View File

@ -73,7 +73,7 @@ class EdudziennikWebStart(override val data: DataEdudziennik,
EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach {
val id = it[1].trim()
val name = it[2].trim()
data.getSubject(id, name)
data.getSubject(id.crc32(), name)
}
}
}

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
@ -89,7 +90,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik,
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
?: return@forEachIndexed
val subjectName = subjectElement.text().trim()
val subject = data.getSubject(subjectId, subjectName)
val subject = data.getSubject(subjectId.crc32(), subjectName)
/* Getting teacher */

View File

@ -18,6 +18,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050
const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100
const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200
const val ENDPOINT_MOBIDZIENNIK_WEB_HOMEWORK = 2300 // not used as an endpoint
const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
val MobidziennikFeatures = listOf(
@ -38,6 +39,12 @@ val MobidziennikFeatures = listOf(
/**
* Timetable - web scraping - does nothing if the API_MAIN timetable is enough.
*/
Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_TIMETABLE, listOf(
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_MOBIDZIENNIK_WEB
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)),
/**
* Agenda - "API" + web scraping.
*/

View File

@ -84,6 +84,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
MobidziennikWebManuals(data, lastSync, onSuccess)
}*/
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
MobidziennikWebTimetable(data, lastSync, onSuccess)
}
else -> onSuccess(endpointId)
}
}

View File

@ -48,7 +48,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
//syncWeeks.clear()
//syncWeeks += Date.fromY_m_d("2019-12-19")
syncWeeks.minBy { it.value }?.let {
syncWeeks.minByOrNull { it.value }?.let {
data.toRemove.add(DataRemoveModel.Attendance.from(it))
}

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
import java.net.URLEncoder
class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
override val lastSync: Long?,
@ -27,7 +28,8 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
}
init {
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=+") { text ->
val query = URLEncoder.encode(data.profile?.studentNameLong ?: "a", "UTF-8")
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=$query") { text ->
MobidziennikLuckyNumberExtractor(data, text)
val doc = Jsoup.parse(text)

View File

@ -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("&lt;small&gt;", "$")
.replace("&lt;/small&gt;", "$")
.replace("&lt;br /&gt;", "\n")
.replace("&lt;br/&gt;", "\n")
.replace("&lt;br&gt;", "\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
}
}
}
}

View File

@ -81,39 +81,4 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
val loginShort: String?
get() = studentLogin?.split('@')?.get(0)
fun getSubject(name: String): Subject {
val id = name.crc32()
return subjectList.singleOrNull { it.id == id } ?: run {
val subject = Subject(profileId, id, name, name)
subjectList.put(id, subject)
subject
}
}
fun getTeacher(firstName: String, lastName: String): Teacher {
val name = "$firstName $lastName".fixName()
return teacherList.singleOrNull { it.fullName == name } ?: run {
val id = name.crc32()
val teacher = Teacher(profileId, id, firstName, lastName)
teacherList.put(id, teacher)
teacher
}
}
fun getTeam(name: String? = null): Team {
if (name == "cała klasa" || name == null) return teamClass ?: run {
val id = className!!.crc32()
val teamCode = "$schoolShortName:$className"
val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1)
teamList.put(id, team)
return team
} else {
val id = name.crc32()
val teamCode = "$schoolShortName:$name"
val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1)
teamList.put(id, team)
return team
}
}
}

View File

@ -36,7 +36,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List<JsonObject>)
}
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
val subject = data.getSubject(subjectName)
val subject = data.getSubject(null, subjectName)
val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis
else System.currentTimeMillis()

View File

@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
val subject = data.getSubject(subjectName)
val subject = data.getSubject(null, subjectName)
val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis }
?: System.currentTimeMillis()

View File

@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie,
init {
apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
data.getTeam() // Save the class team when it doesn't exist.
// Save the class team when it doesn't exist.
data.getTeam(
id = null,
name = data.className ?: "",
schoolCode = data.schoolShortName ?: "",
isTeamClass = true
)
json.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) }
json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) }

View File

@ -43,14 +43,21 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List<JsonObject>) {
val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
?: return@forEach
val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach
val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) }
val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(null, it) }
?: return@forEach
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach
val team = lesson.getString("Group")?.let {
data.getTeam(
id = null,
name = it,
schoolCode = data.schoolShortName ?: "",
isTeamClass = it == "cała klasa"
)
} ?: return@forEach
val classroom = lesson.getString("Room")
Lesson(data.profileId, -1).also {

View File

@ -222,6 +222,16 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
private var mSenderAddressHash: String? = null
var senderAddressHash: String?
get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash }
set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value }
private var mSenderAddressName: String? = null
var senderAddressName: String?
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName }
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value }
val apiUrl: String?
get() {
val url = when (apiToken[symbol]?.substring(0, 3)) {

View File

@ -38,7 +38,7 @@ class VulcanHebeAttendance(
lastSync = lastSync
) { list, _ ->
list.forEach { attendance ->
val id = attendance.getLong("AuxPresenceId") ?: return@forEach
val id = attendance.getLong("Id") ?: return@forEach
val type = attendance.getJsonObject("PresenceType") ?: return@forEach
val baseType = getBaseType(type)
val typeName = type.getString("Name") ?: return@forEach

View File

@ -97,6 +97,10 @@ class VulcanHebeMain(
val studentSemesterId = period.getInt("Id") ?: return@forEach
val studentSemesterNumber = period.getInt("Number") ?: return@forEach
val senderEntry = student.getJsonObject("SenderEntry")
val senderAddressName = senderEntry.getString("Address")
val senderAddressHash = senderEntry.getString("AddressHash")
val hebeContext = student.getString("Context")
val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true)
@ -143,6 +147,8 @@ class VulcanHebeMain(
studentData["schoolSymbol"] = schoolSymbol
studentData["schoolShort"] = schoolShort
studentData["schoolName"] = schoolCode
studentData["senderAddressName"] = senderAddressName
studentData["senderAddressHash"] = senderAddressHash
studentData["hebeContext"] = hebeContext
}
dateSemester1Start?.let {

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonObject
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
@ -27,6 +28,22 @@ class VulcanHebeSendMessage(
}
init {
if (data.senderAddressName == null || data.senderAddressHash == null) {
VulcanHebeMain(data).getStudents(data.profile, null) {
if (data.senderAddressName == null || data.senderAddressHash == null) {
data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY)
}
else {
sendMessage()
}
}
}
else {
sendMessage()
}
}
private fun sendMessage() {
val recipientsArray = JsonArray()
recipients.forEach { teacher ->
recipientsArray += JsonObject(
@ -40,10 +57,10 @@ class VulcanHebeSendMessage(
val senderName = (profile?.accountName ?: profile?.studentNameLong)
?.swapFirstLastName() ?: ""
val sender = JsonObject(
"Address" to senderName,
"Address" to data.senderAddressName,
"LoginId" to data.studentLoginId.toString(),
"Initials" to senderName.getNameInitials(),
"AddressHash" to senderName.sha1Hex()
"AddressHash" to data.senderAddressHash
)
apiPost(

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
@ -47,7 +48,7 @@ class VulcanHebeTimetable(
?: previousWeekStart
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
val lastSync = null
val lastSync = 0L
apiGetList(
TAG,
@ -106,6 +107,8 @@ class VulcanHebeTimetable(
"Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}"
)
data.toRemove.add(DataRemoveModel.Timetable.between(dateFrom, dateTo))
data.lessonList.addAll(lessonList)
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)

View File

@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models
import android.util.LongSparseArray
import android.util.SparseArray
import androidx.core.util.set
import androidx.core.util.size
import androidx.room.OnConflictStrategy
import com.google.gson.JsonObject
@ -376,4 +377,108 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
fun startProgress(stringRes: Int) {
callback.onStartProgress(stringRes)
}
/* _ _ _ _ _
| | | | | (_) |
| | | | |_ _| |___
| | | | __| | / __|
| |__| | |_| | \__ \
\____/ \__|_|_|__*/
fun getSubject(id: Long?, name: String, shortName: String = name): Subject {
var subject = subjectList.singleOrNull { it.id == id }
if (subject == null)
subject = subjectList.singleOrNull { it.longName == name }
if (subject == null)
subject = subjectList.singleOrNull { it.shortName == name }
if (subject == null) {
subject = Subject(
profileId,
id ?: name.crc32(),
name,
shortName
)
subjectList[subject.id] = subject
}
return subject
}
fun getTeam(id: Long?, name: String, schoolCode: String, isTeamClass: Boolean = false): Team {
if (isTeamClass && teamClass != null)
return teamClass as Team
var team = teamList.singleOrNull { it.id == id }
val namePlain = name.replace(" ", "")
if (team == null)
team = teamList.singleOrNull { it.name.replace(" ", "") == namePlain }
if (team == null) {
team = Team(
profileId,
id ?: name.crc32(),
name,
if (isTeamClass) Team.TYPE_CLASS else Team.TYPE_VIRTUAL,
"$schoolCode:$name",
-1
)
teamList[team.id] = team
}
return team
}
fun getTeacher(firstName: String, lastName: String, loginId: String? = null): Teacher {
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
return validateTeacher(teacher, firstName, lastName, loginId)
}
fun getTeacher(firstNameChar: Char, lastName: String, loginId: String? = null): Teacher {
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId)
}
fun getTeacherByLastFirst(nameLastFirst: String, loginId: String? = null): Teacher {
val nameParts = nameLastFirst.split(" ")
return if (nameParts.size == 1)
getTeacher(nameParts[0], "", loginId)
else
getTeacher(nameParts[1], nameParts[0], loginId)
}
fun getTeacherByFirstLast(nameFirstLast: String, loginId: String? = null): Teacher {
val nameParts = nameFirstLast.split(" ")
return if (nameParts.size == 1)
getTeacher(nameParts[0], "", loginId)
else
getTeacher(nameParts[0], nameParts[1], loginId)
}
fun getTeacherByFDotLast(nameFDotLast: String, loginId: String? = null): Teacher {
val nameParts = nameFDotLast.split(".")
return if (nameParts.size == 1)
getTeacher(nameParts[0], "", loginId)
else
getTeacher(nameParts[0][0], nameParts[1], loginId)
}
fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String, loginId: String? = null): Teacher {
val nameParts = nameFDotSpaceLast.split(".")
return if (nameParts.size == 1)
getTeacher(nameParts[0], "", loginId)
else
getTeacher(nameParts[0][0], nameParts[1], loginId)
}
private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String, loginId: String?): Teacher {
val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).apply {
id = fullName.crc32()
teacherList[id] = this
}
return obj.also {
if (loginId != null && it.loginId != null)
it.loginId = loginId
if (firstName.length > 1)
it.name = firstName
it.surname = lastName
}
}
}

View File

@ -22,10 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.ApiCacheIntercept
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
import pl.szczodrzynski.edziennik.data.db.entity.Notification
@ -373,6 +370,15 @@ class SzkolnyApi(val app: App) : CoroutineScope {
throw SzkolnyApiException(null)
}
@Throws(Exception::class)
fun getContributors(): ContributorsResponse {
val response = api.contributors().execute()
if (response.isSuccessful && response.body() != null) {
return parseResponse(response)
}
throw SzkolnyApiException(null)
}
@Throws(Exception::class)
fun getFirebaseToken(registerName: String): String {
val response = api.firebaseToken(registerName).execute()

View File

@ -27,6 +27,9 @@ interface SzkolnyService {
@POST("appUser")
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
@GET("contributors/android")
fun contributors(): Call<ApiResponse<ContributorsResponse>>
@GET("updates/app")
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>

View File

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

View File

@ -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
}

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
companion object {
@ -42,13 +43,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
val text = app.getString(
R.string.notification_lesson_change_format,
lesson.getDisplayChangeType(app),
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString,
lesson.displayDate?.formattedString ?: "",
lesson.changeSubjectName
)
val textLong = app.getString(
R.string.notification_lesson_change_long_format,
lesson.getDisplayChangeType(app),
lesson.displayDate?.formattedString ?: "-",
lesson.displayDate?.weekDay?.let { Week.getFullDayName(it) } ?: "-",
lesson.changeSubjectName,
lesson.changeTeacherName
)
notifications += Notification(
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
textLong = textLong,
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
profileId = lesson.profileId,
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
@ -79,11 +89,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
event.date.formattedString,
event.subjectLongName
)
val textLong = app.getString(
R.string.notification_event_long_format,
event.typeName ?: "-",
event.subjectLongName ?: "-",
event.date.formattedString,
Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200)
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
textLong = textLong,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
@ -102,11 +122,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
event.date.formattedString,
event.topic
)
val textLong = app.getString(
R.string.notification_shared_event_long_format,
event.sharedByName,
event.typeName ?: "-",
event.subjectLongName ?: "-",
event.date.formattedString,
Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200)
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
textLong = textLong,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
@ -130,10 +161,20 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
gradeName,
grade.subjectLongName
)
val textLong = app.getString(
R.string.notification_grade_long_format,
gradeName,
grade.weight.toString(),
grade.subjectLongName ?: "-",
grade.category ?: "-",
grade.description ?: "-",
grade.teacherName ?: "-"
)
notifications += Notification(
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
text = text,
textLong = textLong,
type = Notification.TYPE_NEW_GRADE,
profileId = grade.profileId,
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
@ -158,10 +199,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
notice.teacherName,
Date.fromMillis(notice.addedDate).formattedString
)
val textLong = app.getString(
R.string.notification_notice_long_format,
noticeTypeStr,
notice.teacherName ?: "-",
notice.text.take(200)
)
notifications += Notification(
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
text = text,
textLong = textLong,
type = Notification.TYPE_NEW_NOTICE,
profileId = notice.profileId,
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
@ -193,10 +241,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
attendance.subjectLongName,
attendance.date.formattedString
)
val textLong = app.getString(
R.string.notification_attendance_long_format,
attendanceTypeStr,
attendance.date.formattedString,
attendance.startTime?.stringHM ?: "-",
attendance.lessonNumber ?: "-",
attendance.subjectLongName ?: "-",
attendance.teacherName ?: "-",
attendance.lessonTopic ?: "-"
)
notifications += Notification(
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
text = text,
textLong = textLong,
type = Notification.TYPE_NEW_ATTENDANCE,
profileId = attendance.profileId,
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,

View File

@ -8,6 +8,9 @@ import android.util.SparseIntArray
import androidx.core.app.NotificationCompat
import androidx.core.util.forEach
import androidx.core.util.set
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.utils.models.Time
@ -107,6 +110,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.InboxStyle()
.also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -137,8 +144,11 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
.setTicker("${it.profileName}: ${it.title}")
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply {
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text))
.bigText(it.textLong ?: it.text))
.setWhen(it.addedDate)
.addDefaults()
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
@ -160,6 +170,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.addDefaults()
.setGroupSummary(true)
.setContentIntent(summaryIntent)

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class,
TimetableManual::class,
Metadata::class
], version = 91)
], version = 93)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
@ -176,7 +176,9 @@ abstract class AppDb : RoomDatabase() {
Migration88(),
Migration89(),
Migration90(),
Migration91()
Migration91(),
Migration92(),
Migration93()
).allowMainThreadQueries().build()
}
}

View File

@ -9,30 +9,9 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
@Dao
abstract class EventTypeDao {
@ -58,19 +37,18 @@ abstract class EventTypeDao {
abstract val allNow: List<EventType>
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
val typeList = listOf(
EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK),
EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT),
EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM),
EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ),
EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY),
EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT),
EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING),
EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION),
EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING),
EventType(profileId, TYPE_CLASS_EVENT, context.getString(R.string.event_class_event), COLOR_CLASS_EVENT),
EventType(profileId, TYPE_INFORMATION, context.getString(R.string.event_information), COLOR_INFORMATION)
)
var order = 100
val colorMap = EventType.getTypeColorMap()
val typeList = EventType.getTypeNameMap().map { (id, name) ->
EventType(
profileId = profileId,
id = id,
name = context.getString(name),
color = colorMap[id] ?: COLOR_DEFAULT,
order = order++,
source = SOURCE_DEFAULT
)
}
addAll(typeList)
return typeList
}

View File

@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
"LIMIT 1")
fun getBetweenDates(dateFrom: Date, dateTo: Date) =
getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY")
fun getChanges(profileId: Int) =
getRaw("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY")
// GET ALL - NOW
fun getAllNow(profileId: Int) =

View File

@ -45,6 +45,7 @@ open class Event(
var addedDate: Long = System.currentTimeMillis()
) : Keepable() {
companion object {
const val TYPE_ELEARNING = -5L
const val TYPE_UNDEFINED = -2L
const val TYPE_HOMEWORK = -1L
const val TYPE_DEFAULT = 0L
@ -57,7 +58,7 @@ open class Event(
const val TYPE_READING = 7L
const val TYPE_CLASS_EVENT = 8L
const val TYPE_INFORMATION = 9L
const val TYPE_TEACHER_ABSENCE = 10L
const val COLOR_ELEARNING = 0xfff57f17.toInt()
const val COLOR_HOMEWORK = 0xff795548.toInt()
const val COLOR_DEFAULT = 0xffffc107.toInt()
const val COLOR_EXAM = 0xfff44336.toInt()
@ -69,7 +70,6 @@ open class Event(
const val COLOR_READING = 0xFFFFEB3B.toInt()
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
const val COLOR_INFORMATION = 0xff039be5.toInt()
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
}
@ColumnInfo(name = "eventAddedManually")
@ -116,14 +116,7 @@ open class Event(
var showAsUnseen: Boolean? = null
val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set(
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
get() = date.getAsCalendar(time)
val endTimeCalendar: Calendar
get() = startTimeCalendar.also {

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.entity;
import android.graphics.Color;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
@Entity(tableName = "eventTypes",
primaryKeys = {"profileId", "eventType"})
public class EventType {
public int profileId;
@ColumnInfo(name = "eventType")
public long id;
@ColumnInfo(name = "eventTypeName")
public String name;
@ColumnInfo(name = "eventTypeColor")
public int color;
public EventType(int profileId, long id, String name, int color) {
this.profileId = profileId;
this.id = id;
this.name = name;
this.color = color;
}
public EventType(int profileId, int id, String name, String color) {
this(profileId, id, name, Color.parseColor(color));
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-19.
*/
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
@Entity(
tableName = "eventTypes",
primaryKeys = ["profileId", "eventType"]
)
class EventType(
val profileId: Int,
@ColumnInfo(name = "eventType")
val id: Long,
@ColumnInfo(name = "eventTypeName")
val name: String,
@ColumnInfo(name = "eventTypeColor")
val color: Int,
@ColumnInfo(name = "eventTypeOrder")
var order: Int = id.toInt(),
@ColumnInfo(name = "eventTypeSource")
val source: Int = SOURCE_REGISTER
) {
companion object {
const val SOURCE_DEFAULT = 0
const val SOURCE_REGISTER = 1
const val SOURCE_CUSTOM = 2
const val SOURCE_SHARED = 3
fun getTypeColorMap() = mapOf(
TYPE_ELEARNING to COLOR_ELEARNING,
TYPE_HOMEWORK to COLOR_HOMEWORK,
TYPE_DEFAULT to COLOR_DEFAULT,
TYPE_EXAM to COLOR_EXAM,
TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ,
TYPE_ESSAY to COLOR_ESSAY,
TYPE_PROJECT to COLOR_PROJECT,
TYPE_PT_MEETING to COLOR_PT_MEETING,
TYPE_EXCURSION to COLOR_EXCURSION,
TYPE_READING to COLOR_READING,
TYPE_CLASS_EVENT to COLOR_CLASS_EVENT,
TYPE_INFORMATION to COLOR_INFORMATION
)
fun getTypeNameMap() = mapOf(
TYPE_ELEARNING to R.string.event_type_elearning,
TYPE_HOMEWORK to R.string.event_type_homework,
TYPE_DEFAULT to R.string.event_other,
TYPE_EXAM to R.string.event_exam,
TYPE_SHORT_QUIZ to R.string.event_short_quiz,
TYPE_ESSAY to R.string.event_essay,
TYPE_PROJECT to R.string.event_project,
TYPE_PT_MEETING to R.string.event_pt_meeting,
TYPE_EXCURSION to R.string.event_excursion,
TYPE_READING to R.string.event_reading,
TYPE_CLASS_EVENT to R.string.event_class_event,
TYPE_INFORMATION to R.string.event_information
)
}
}

View File

@ -10,6 +10,8 @@ import android.content.Intent
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.JsonObject
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.MainActivity
@Entity(tableName = "notifications")
@ -19,6 +21,7 @@ data class Notification(
val title: String,
val text: String,
val textLong: String? = null,
val type: Int,
@ -96,4 +99,19 @@ data class Notification(
fillIntent(intent)
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
}
fun getLargeIcon(): IIcon = when (type) {
TYPE_TIMETABLE_LESSON_CHANGE -> CommunityMaterial.Icon3.cmd_timetable
TYPE_NEW_GRADE -> CommunityMaterial.Icon3.cmd_numeric_5_box_outline
TYPE_NEW_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
TYPE_NEW_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
TYPE_NEW_SHARED_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
TYPE_NEW_SHARED_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
TYPE_NEW_MESSAGE -> CommunityMaterial.Icon.cmd_email_outline
TYPE_NEW_NOTICE -> CommunityMaterial.Icon.cmd_emoticon_outline
TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline
TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline
TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline
else -> CommunityMaterial.Icon.cmd_bell_ring_outline
}
}

View File

@ -129,6 +129,9 @@ open class Profile(
val isParent
get() = accountName != null
val accountOwnerName
get() = accountName ?: studentNameLong
val registerName
get() = when (loginStoreType) {
LOGIN_TYPE_LIBRUS -> "librus"

View File

@ -30,7 +30,7 @@ class MessageFull(
@Ignore
var filterWeight = 0
@Ignore
var searchHighlightText: String? = null
var searchHighlightText: CharSequence? = null
// metadata
var seen = false

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-15.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_REGISTER
import pl.szczodrzynski.edziennik.getInt
class Migration92 : Migration(91, 92) {
override fun migrate(database: SupportSQLiteDatabase) {
// make eventTypeName not nullable
database.execSQL("ALTER TABLE eventTypes RENAME TO _eventTypes;")
database.execSQL("CREATE TABLE eventTypes (" +
"profileId INTEGER NOT NULL, " +
"eventType INTEGER NOT NULL, " +
"eventTypeName TEXT NOT NULL, " +
"eventTypeColor INTEGER NOT NULL, " +
"PRIMARY KEY(profileId,eventType)" +
");")
database.execSQL("INSERT INTO eventTypes " +
"(profileId, eventType, eventTypeName, eventTypeColor) " +
"SELECT profileId, eventType, eventTypeName, eventTypeColor " +
"FROM _eventTypes;")
database.execSQL("DROP TABLE _eventTypes;")
// add columns for order and source
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeOrder INTEGER NOT NULL DEFAULT 0;")
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeSource INTEGER NOT NULL DEFAULT 0;")
// migrate existing types to show correct order and source
database.execSQL("UPDATE eventTypes SET eventTypeOrder = eventType + 102;")
database.execSQL("UPDATE eventTypes SET eventTypeSource = $SOURCE_REGISTER WHERE eventType > $TYPE_INFORMATION;")
// add new e-learning type
val cursor = database.query("SELECT profileId FROM profiles;")
cursor.use {
while (it.moveToNext()) {
val values = ContentValues().apply {
put("profileId", it.getInt("profileId"))
put("eventType", TYPE_ELEARNING)
put("eventTypeName", "lekcja online")
put("eventTypeColor", COLOR_ELEARNING)
put("eventTypeOrder", 100)
put("eventTypeSource", SOURCE_DEFAULT)
}
database.insert("eventTypes", SQLiteDatabase.CONFLICT_REPLACE, values)
}
}
}
}

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-5-26.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration93 : Migration(92, 93) {
override fun migrate(database: SupportSQLiteDatabase) {
// notifications - long text
database.execSQL("ALTER TABLE notifications ADD COLUMN textLong TEXT DEFAULT NULL;")
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.dialogs
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.MessagesConfigDialogBinding
class MessagesConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "MessagesConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: MessagesConfigDialogBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = MessagesConfigDialogBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_messages_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
b.config = profileConfig
b.greetingText.setText(
profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
)
}
private fun saveConfig() {
val greetingText = b.greetingText.text?.toString()?.trim()
if (greetingText.isNullOrEmpty())
profileConfig.messagesGreetingText = null
else
profileConfig.messagesGreetingText = "\n\n$greetingText"
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.agenda
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import java.util.*
class AgendaConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "AgendaConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: DialogConfigAgendaBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = DialogConfigAgendaBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_agenda_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
b.config = profileConfig
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
b.eventSharingEnabled.isChecked = app.profile.enableSharedEvents
&& app.profile.registration == REGISTRATION_ENABLED
b.eventSharingEnabled.onChange { _, isChecked ->
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) {
b.eventSharingEnabled.isChecked = false
val dialog = RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
b.eventSharingEnabled.isChecked = enabled
setEventSharingEnabled(enabled)
}, onShowListener, onDismissListener)
dialog.showEnableDialog()
return@onChange
}
setEventSharingEnabled(isChecked)
}
}
private fun setEventSharingEnabled(enabled: Boolean) {
if (enabled == app.profile.enableSharedEvents)
return
app.profile.enableSharedEvents = enabled
app.profileSave()
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.event_sharing)
.setMessage(
if (enabled)
R.string.settings_register_shared_events_dialog_enabled_text
else
R.string.settings_register_shared_events_dialog_disabled_text
)
.setPositiveButton(R.string.ok, null)
.show()
}
private fun saveConfig() {
}
}

View File

@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.day
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
@ -19,6 +19,10 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -29,6 +33,7 @@ class DayDialog(
val activity: AppCompatActivity,
val profileId: Int,
val date: Date,
val eventTypeId: Long? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
@ -109,38 +114,51 @@ class DayDialog(
}
lessonChanges.ifNotEmpty {
b.lessonChangeContainer.root.visibility = View.VISIBLE
b.lessonChangeContainer.lessonChangeCount.text = it.size.toString()
LessonChangesEventRenderer().render(
b.lessonChanges, LessonChangesEvent(
profileId = profileId,
date = date,
count = it.size,
showBadge = false
)
)
b.lessonChangeLayout.onClick {
b.lessonChangesFrame.onClick {
LessonChangeDialog(
activity,
profileId,
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
activity,
profileId,
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
}
b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty()
val teacherAbsences = withContext(Dispatchers.Default) {
app.db.teacherAbsenceDao().getAllByDateNow(profileId, date)
}
teacherAbsences.ifNotEmpty {
b.teacherAbsenceContainer.root.visibility = View.VISIBLE
b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString()
TeacherAbsenceEventRenderer().render(
b.teacherAbsence, TeacherAbsenceEvent(
profileId = profileId,
date = date,
count = it.size
)
)
b.teacherAbsenceLayout.onClick {
b.teacherAbsenceFrame.onClick {
TeacherAbsenceDialog(
activity,
profileId,
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
activity,
profileId,
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
}
b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty()
adapter = EventListAdapter(
activity,
@ -169,8 +187,12 @@ class DayDialog(
}
)
app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events ->
adapter.items = events
app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
adapter.items = if (eventTypeId != null)
events.filter { it.type == eventTypeId }
else
events
if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter
b.eventsView.apply {
@ -189,6 +211,6 @@ class DayDialog(
b.eventsView.visibility = View.GONE
b.eventsNoData.visibility = View.VISIBLE
}
})
}
}}
}

View File

@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext
class EventDetailsDialog(
val activity: AppCompatActivity,
val event: EventFull,
var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
@ -46,6 +46,8 @@ class EventDetailsDialog(
private var removeEventDialog: AlertDialog? = null
private val eventShared = event.sharedBy != null
private val eventOwn = event.sharedBy == "self"
private val manager
get() = app.eventManager
private val job = Job()
override val coroutineContext: CoroutineContext
@ -92,6 +94,10 @@ class EventDetailsDialog(
b.eventShared = eventShared
b.eventOwn = eventOwn
if (!event.seen) {
manager.markAsSeen(event)
}
val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
@ -100,6 +106,8 @@ class EventDetailsDialog(
}
catch (_: Exception) {}
manager.setLegendText(b.legend, event)
b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf(
@ -135,6 +143,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) {
app.db.eventDao().replace(event)
}
update()
b.checkDoneButton.isChecked = true
}
.setNegativeButton(R.string.cancel, null)
@ -145,6 +154,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) {
app.db.eventDao().replace(event)
}
update()
}
}
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
@ -156,6 +166,14 @@ class EventDetailsDialog(
activity,
event.profileId,
editingEvent = event,
onSaveListener = {
if (it == null) {
dialog.dismiss()
return@EventManualDialog
}
event = it
update()
},
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
@ -199,10 +217,14 @@ class EventDetailsDialog(
}
b.downloadButton.attachToastHint(R.string.hint_download_again)
BetterLink.attach(b.topic, onActionSelected = dialog::dismiss)
b.topic.text = event.topic
BetterLink.attach(b.topic) {
dialog.dismiss()
event.teacherName?.let { name ->
BetterLink.attach(
b.teacherName,
teachers = mapOf(event.teacherId to name),
onActionSelected = dialog::dismiss
)
}
if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) {
@ -220,10 +242,7 @@ class EventDetailsDialog(
b.bodyTitle.isVisible = true
b.bodyProgressBar.isVisible = false
b.body.isVisible = true
b.body.text = event.homeworkBody
BetterLink.attach(b.body) {
dialog.dismiss()
}
BetterLink.attach(b.body, onActionSelected = dialog::dismiss)
}
if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) {
@ -322,8 +341,6 @@ class EventDetailsDialog(
removeEventDialog?.dismiss()
dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA)
activity.reloadTarget()
}
private fun openInCalendar() { launch {

View File

@ -33,7 +33,8 @@ class EventListAdapter(
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope {
private val app = context.applicationContext as App
private val manager = app.eventManager
private val manager
get() = app.eventManager
private val job = Job()
override val coroutineContext: CoroutineContext
@ -67,7 +68,7 @@ class EventListAdapter(
b.simpleMode = simpleMode
b.topic.text = event.topic
manager.setEventTopic(b.topic, event, showType = false)
b.topic.maxLines = if (simpleMode) 2 else 3
b.details.text = mutableListOf<CharSequence?>(
@ -102,8 +103,6 @@ class EventListAdapter(
}
b.editButton.attachToastHint(R.string.hint_edit_event)
b.isDone.isVisible = event.isDone
if (event.showAsUnseen == null)
event.showAsUnseen = !event.seen

View File

@ -4,8 +4,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@ -20,23 +18,18 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
@ -49,6 +42,7 @@ class EventManualDialog(
val defaultTime: Time? = null,
val defaultType: Long? = null,
val editingEvent: EventFull? = null,
val onSaveListener: ((event: EventFull?) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
@ -323,57 +317,41 @@ class EventManualDialog(
selectDefault(defaultLesson?.displayTeacherId)
}
with (b.typeDropdown) {
db = app.db
profileId = this@EventManualDialog.profileId
loadItems()
selectDefault(editingEvent?.type)
selectDefault(defaultType)
val deferred = async(Dispatchers.Default) {
// get the event type list
var eventTypes = app.db.eventTypeDao().getAllNow(profileId)
if (eventTypes.none { it.id in -1L..10L }) {
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
onTypeSelected = {
b.typeColor.background.setTintColor(it.color)
customColor = null
}
b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
}
deferred.await()
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited
// copy data from event being edited
editingEvent?.let {
b.topic.setText(it.topic)
b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color
}
if (it.color != null && it.color != -1)
if (it.color != -1)
customColor = it.color
}
b.typeColor.background.setTintColor(
customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
)
// copy IDs from the LessonFull
defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId)
}
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick {
val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
val currentColor = customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor)
.create()
@ -381,7 +359,7 @@ class EventManualDialog(
object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
b.typeColor.background.setTintColor(color)
customColor = color
}
})
@ -416,11 +394,11 @@ class EventManualDialog(
private fun saveEvent() {
val date = b.dateDropdown.getSelected() as? Date
val timeSelected = b.timeDropdown.getSelected()
val teamId = b.teamDropdown.getSelected() as? Long
val type = b.typeDropdown.selected?.id
val team = b.teamDropdown.getSelected()
val type = b.typeDropdown.getSelected()
val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.getSelected() as? Long
val teacherId = b.teacherDropdown.getSelected()
val subject = b.subjectDropdown.getSelected() as? Subject
val teacher = b.teacherDropdown.getSelected()
val share = b.shareSwitch.isChecked
@ -451,7 +429,7 @@ class EventManualDialog(
isError = true
}
if (share && teamId == null) {
if (share && team == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
isError = true
@ -487,10 +465,10 @@ class EventManualDialog(
time = startTime,
topic = topic,
color = customColor,
type = type ?: Event.TYPE_DEFAULT,
teacherId = teacherId ?: -1,
subjectId = subjectId ?: -1,
teamId = teamId ?: -1,
type = type?.id ?: Event.TYPE_DEFAULT,
teacherId = teacher?.id ?: -1,
subjectId = subject?.id ?: -1,
teamId = team?.id ?: -1,
addedDate = editingEvent?.addedDate ?: System.currentTimeMillis()
).also {
it.addedManually = true
@ -498,7 +476,7 @@ class EventManualDialog(
val metadataObject = Metadata(
profileId,
when (type) {
when (type?.id) {
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
else -> Metadata.TYPE_EVENT
},
@ -597,10 +575,14 @@ class EventManualDialog(
}
}
onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also {
it.subjectLongName = (b.subjectDropdown.getSelected() as? Subject)?.longName
it.teacherName = b.teacherDropdown.getSelected()?.fullName
it.teamName = b.teamDropdown.getSelected()?.name
it.typeName = b.typeDropdown.getSelected()?.name
})
dialog.dismiss()
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
}
private fun finishRemoving() {
editingEvent ?: return
@ -611,9 +593,8 @@ class EventManualDialog(
}
removeEventDialog?.dismiss()
onSaveListener?.invoke(null)
dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
}
}

View File

@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
@ -68,6 +69,14 @@ class GradeDetailsDialog(
GradesConfigDialog(activity, reloadOnDismiss = true)
}
grade.teacherName?.let { name ->
BetterLink.attach(
b.teacherName,
teachers = mapOf(grade.teacherId to name),
onActionSelected = dialog::dismiss
)
}
launch {
val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)

View File

@ -4,11 +4,14 @@
package pl.szczodrzynski.edziennik.ui.dialogs.timetable
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Intent
import android.graphics.*
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.view.View.MeasureSpec
@ -373,25 +376,31 @@ class GenerateBlockTimetableDialog(
val today = Date.getToday().stringY_m_d
val now = Time.getNow().stringH_M_S
val filename = "plan_lekcji_${app.profile.name}_${today}_${now}.png"
val resolver: ContentResolver = activity.applicationContext.contentResolver
val values = ContentValues()
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() }
val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
values.put(MediaStore.MediaColumns.RELATIVE_PATH, File(Environment.DIRECTORY_PICTURES, "Szkolny.eu").path)
} else {
val picturesDirectory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Szkolny.eu")
picturesDirectory.mkdirs()
values.put(MediaStore.MediaColumns.DATA, File(picturesDirectory, filename).path)
}
try {
val fos = FileOutputStream(outputFile)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.close()
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return@withContext null
resolver.openOutputStream(uri).use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
uri
} catch (e: Exception) {
Log.e("SAVE_IMAGE", e.message, e)
return@withContext null
}
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile)
} else {
Uri.parse("file://" + outputFile.absolutePath)
}
uri
}
progressDialog.dismiss()

View File

@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
@ -49,7 +50,8 @@ class LessonDetailsDialog(
get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter
private val manager by lazy { app.timetableManager }
private val manager
get() = app.timetableManager
init { run {
if (activity.isFinishing)
@ -216,5 +218,19 @@ class LessonDetailsDialog(
b.eventsNoData.visibility = View.VISIBLE
}
})
lesson.displayTeacherName?.let { name ->
lesson.displayTeacherId ?: return@let
BetterLink.attach(
b.teacherNameView,
teachers = mapOf(lesson.displayTeacherId!! to name),
onActionSelected = dialog::dismiss
)
BetterLink.attach(
b.oldTeacherNameView,
teachers = mapOf(lesson.displayTeacherId!! to name),
onActionSelected = dialog::dismiss
)
}
}
}

View File

@ -12,10 +12,6 @@ import android.widget.Toast
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.applandeo.materialcalendarview.EventDay
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
@ -25,21 +21,14 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -59,7 +48,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main
private var type: Int = Profile.AGENDA_DEFAULT
private var actualDate: Date? = null
private var agendaDefault: AgendaFragmentDefault? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (getActivity() == null || context == null) return null
@ -82,38 +72,61 @@ class AgendaFragment : Fragment(), CoroutineScope {
.withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc)
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener {
.withOnClickListener {
activity.bottomSheet.close()
EventManualDialog(activity, app.profileId, defaultDate = actualDate)
}),
EventManualDialog(
activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
AgendaConfigDialog(activity, true, null, null)
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view)
.withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square)
.withOnClickListener(View.OnClickListener {
.withOnClickListener {
activity.bottomSheet.close()
type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
type =
if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
app.config.forProfile().ui.agendaViewType = type
activity.reloadTarget()
}),
},
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener { launch {
activity.bottomSheet.close()
withContext(Dispatchers.Default) {
App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
.withOnClickListener {
launch {
activity.bottomSheet.close()
withContext(Dispatchers.Default) {
App.db.metadataDao()
.setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
}
Toast.makeText(
activity,
R.string.main_menu_mark_as_read_success,
Toast.LENGTH_SHORT
).show()
}
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
}})
}
)
activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
activity.navView.setFabOnClickListener(View.OnClickListener {
EventManualDialog(activity, app.profileId, defaultDate = actualDate)
})
activity.navView.setFabOnClickListener {
EventManualDialog(
activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
}
activity.gainAttention()
activity.gainAttentionFAB()
@ -124,151 +137,30 @@ class AgendaFragment : Fragment(), CoroutineScope {
}
}
private suspend fun checkEventTypes() {
withContext(Dispatchers.Default) {
val eventTypes = app.db.eventTypeDao().getAllNow(app.profileId).map {
it.id
}
val defaultEventTypes = EventType.getTypeColorMap().keys
if (!eventTypes.containsAll(defaultEventTypes)) {
app.db.eventTypeDao().addDefaultTypes(activity, app.profileId)
}
}
}
private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch {
if (!isAdded)
return@launch
checkEventTypes()
delay(500)
val eventList = mutableListOf<CalendarEvent>()
val minDate = Calendar.getInstance().apply {
add(Calendar.MONTH, -2)
set(Calendar.DAY_OF_MONTH, 1)
}
val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) }
/**
* LESSON CHANGES
*/
if (!isAdded)
return@launch
val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) }
val lessonChangeCounters = mutableListOf<LessonChangeCounter>()
lessons.forEach { lesson ->
lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let {
it.lessonChangeCount += 1
} ?: run {
lessonChangeCounters.add(LessonChangeCounter(
lesson.displayDate ?: return@forEach,
1
))
}
}
lessonChangeCounters.forEach { counter ->
eventList.add(LessonChangeEvent(
counter.lessonChangeDate.inMillis,
0xff78909c.toInt(),
Colors.legibleTextColor(0xff78909c.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.lessonChangeDate,
counter.lessonChangeCount
))
}
/**
* TEACHER ABSENCES
*/
if (!isAdded)
return@launch
val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true)
if (showTeacherAbsences) {
val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) }
val teacherAbsenceCounters = mutableListOf<TeacherAbsenceCounter>()
teacherAbsenceList.forEach { absence ->
val date = absence.dateFrom.clone()
while (date <= absence.dateTo) {
teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let {
it.teacherAbsenceCount += 1
} ?: run {
teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1))
}
date.stepForward(0, 0, 1)
}
}
teacherAbsenceCounters.forEach { counter ->
eventList.add(TeacherAbsenceEvent(
counter.teacherAbsenceDate.inMillis,
0xffff1744.toInt(),
Colors.legibleTextColor(0xffff1744.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.teacherAbsenceDate,
counter.teacherAbsenceCount
))
}
}
/**
* EVENTS
*/
if (!isAdded)
return@launch
val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) }
val unreadEventDates = mutableSetOf<Int>()
events.forEach { event ->
eventList.add(BaseCalendarEvent(
"${event.typeName ?: "wydarzenie"} - ${event.topic}",
"",
(if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) +
(event.subjectLongName?.let { ", $it" } ?: "") +
(event.teacherName?.let { ", $it" } ?: "") +
(event.teamName?.let { ", $it" } ?: ""),
event.eventColor,
Colors.legibleTextColor(event.eventColor),
event.startTimeCalendar,
event.endTimeCalendar,
event.time == null,
event.id,
!event.seen
))
if (!event.seen) unreadEventDates.add(event.date.value)
}
b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem?) {}
override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch {
val date = Date.fromCalendar(calendar)
actualDate = date
// Mark as read scrolled date
if (date.value in unreadEventDates) {
withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) }
unreadEventDates.remove(date.value)
}
}}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is BaseCalendarEvent -> DayDialog(activity, app.profileId, date)
is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date)
}
}
}, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer())
b.progressBar.visibility = View.GONE
agendaDefault = AgendaFragmentDefault(activity, app, b)
agendaDefault?.initView(this@AgendaFragment)
}}}
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
checkEventTypes()
delay(300)
val dayList = mutableListOf<EventDay>()

View File

@ -0,0 +1,310 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import android.util.SparseIntArray
import android.widget.AbsListView
import android.widget.AbsListView.OnScrollListener
import androidx.core.util.forEach
import androidx.core.util.set
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.CalendarManager
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.agenda.AgendaAdapter
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroup
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroupRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class AgendaFragmentDefault(
private val activity: MainActivity,
private val app: App,
private val b: FragmentAgendaDefaultBinding
) : OnScrollListener, CoroutineScope {
companion object {
var selectedDate: Date = Date.getToday()
}
override val coroutineContext = Job() + Dispatchers.Main
private val unreadDates = mutableSetOf<Int>()
private val events = mutableListOf<CalendarEvent>()
private var isInitialized = false
private val profileConfig by lazy { app.config.forProfile().ui }
private val listView
get() = b.agendaDefaultView.agendaView.agendaListView
private val adapter
get() = listView.adapter as? AgendaAdapter
private val manager
get() = CalendarManager.getInstance()
private var scrollState = OnScrollListener.SCROLL_STATE_IDLE
private var updatePending = false
private var notifyPending = false
override fun onScrollStateChanged(view: AbsListView?, newScrollState: Int) {
b.agendaDefaultView.agendaScrollListener.onScrollStateChanged(view, scrollState)
scrollState = newScrollState
if (updatePending) updateData()
if (notifyPending) notifyDataSetChanged()
}
override fun onScroll(
view: AbsListView?,
firstVisibleItem: Int,
visibleItemCount: Int,
totalItemCount: Int
) = b.agendaDefaultView.agendaScrollListener.onScroll(
view,
firstVisibleItem,
visibleItemCount,
totalItemCount
)
/**
* Mark the data as needing update, either after 1 second (when
* not scrolling) or 1 second after scrolling stops.
*/
private fun updateData() = launch {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
updatePending = false
delay(1000)
notifyDataSetChanged()
} else updatePending = true
}
/**
* Notify the adapter about changes, either instantly or after
* scrolling stops.
*/
private fun notifyDataSetChanged() {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
notifyPending = false
adapter?.notifyDataSetChanged()
} else notifyPending = true
}
suspend fun initView(fragment: AgendaFragment) {
isInitialized = false
withContext(Dispatchers.Default) {
if (profileConfig.agendaLessonChanges)
addLessonChanges(events)
if (profileConfig.agendaTeacherAbsence)
addTeacherAbsence(events)
}
app.db.eventDao().getAll(app.profileId).observe(fragment) {
addEvents(events, it)
if (isInitialized)
updateView()
else
initViewPriv()
}
}
private fun initViewPriv() {
val dateStart = app.profile.dateSemester1Start.asCalendar
val dateEnd = app.profile.dateYearEnd.asCalendar
val isCompactMode = profileConfig.agendaCompactMode
b.agendaDefaultView.init(
events,
dateStart,
dateEnd,
Locale.getDefault(),
object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem) {
val c = Calendar.getInstance()
c.time = dayItem.date
if (c.timeInMillis == selectedDate.inMillis) {
DayDialog(activity, app.profileId, selectedDate)
}
}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is AgendaEvent -> EventDetailsDialog(activity, event.event)
is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(
activity,
app.profileId,
date
)
is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId)
is BaseCalendarEvent -> if (event.isPlaceHolder)
DayDialog(activity, app.profileId, date)
}
if (event is BaseEvent && event.showItemBadge) {
val unreadCount = manager.events.count {
it.instanceDay.equals(event.instanceDay) && it.showBadge
}
// only clicked event is unread, remove the day badge
if (unreadCount == 1 && event.showBadge) {
event.dayReference.showBadge = false
unreadDates.remove(date.value)
}
setAsRead(event)
}
}
override fun onScrollToDate(calendar: Calendar) {
selectedDate = Date.fromCalendar(calendar)
// Mark as read scrolled date
if (selectedDate.value in unreadDates) {
setAsRead(calendar)
activity.launch(Dispatchers.Default) {
app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true)
}
unreadDates.remove(selectedDate.value)
}
}
},
AgendaEventRenderer(app.eventManager, isCompactMode),
AgendaEventGroupRenderer(),
LessonChangesEventRenderer(),
TeacherAbsenceEventRenderer()
)
listView.setOnScrollListener(this)
isInitialized = true
b.progressBar.isVisible = false
}
private fun updateView() {
manager.events.clear()
manager.loadEvents(events, BaseCalendarEvent())
adapter?.updateEvents(manager.events)
//listView.scrollToCurrentDate(selectedDate.asCalendar)
}
private fun setAsRead(date: Calendar) {
// get all events matching the date
val events = manager.events.filter {
if (it.instanceDay.equals(date) && it.showBadge && it is AgendaEvent) {
// hide the day badge for the date
it.dayReference.showBadge = false
return@filter true
}
false
}
// set this date's events as read
setAsRead(*events.toTypedArray())
}
private fun setAsRead(vararg event: CalendarEvent) {
// hide per-event badges
for (e in event) {
events.firstOrNull {
it == e
}?.showBadge = false
e.showBadge = false
}
listView.setOnScrollListener(this)
updateData()
}
private fun addEvents(
events: MutableList<CalendarEvent>,
eventList: List<EventFull>
) {
events.removeAll { it is AgendaEvent || it is AgendaEventGroup }
if (!profileConfig.agendaGroupByType) {
events += eventList.map {
if (!it.seen)
unreadDates.add(it.date.value)
AgendaEvent(it)
}
return
}
eventList.groupBy {
it.date.value to it.type
}.forEach { (_, list) ->
val event = list.first()
if (list.size == 1) {
if (!event.seen)
unreadDates.add(event.date.value)
events += AgendaEvent(event)
} else {
events.add(0, AgendaEventGroup(
profileId = event.profileId,
date = event.date,
typeId = event.type,
typeName = event.typeName ?: "-",
typeColor = event.typeColor ?: event.eventColor,
count = list.size,
showBadge = list.any { !it.seen }
))
}
}
}
private fun addLessonChanges(events: MutableList<CalendarEvent>) {
val lessons = app.db.timetableDao().getChangesNow(app.profileId)
val grouped = lessons.groupBy {
it.displayDate
}
events += grouped.mapNotNull { (date, changes) ->
LessonChangesEvent(
app.profileId,
date = date ?: return@mapNotNull null,
count = changes.size,
showBadge = changes.any { !it.seen }
)
}
}
private fun addTeacherAbsence(events: MutableList<CalendarEvent>) {
val teacherAbsence = app.db.teacherAbsenceDao().getAllNow(app.profileId)
val countMap = SparseIntArray()
for (absence in teacherAbsence) {
while (absence.dateFrom <= absence.dateTo) {
countMap[absence.dateFrom.value] += 1
absence.dateFrom.stepForward(0, 0, 1)
}
}
countMap.forEach { dateInt, count ->
events += TeacherAbsenceEvent(
app.profileId,
date = Date.fromValue(dateInt),
count = count
)
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-9.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import java.util.*
open class BaseEvent(
private val id: Long,
private val time: Calendar,
private val color: Int,
private var showBadge: Boolean,
var showItemBadge: Boolean = showBadge
) : CalendarEvent {
override fun copy() = BaseEvent(id, time, color, showBadge)
private lateinit var date: Calendar
override fun getInstanceDay() = date
override fun setInstanceDay(value: Calendar) {
date = value
}
private lateinit var dayReference: IDayItem
override fun getDayReference() = dayReference
override fun setDayReference(value: IDayItem) {
dayReference = value
}
private lateinit var weekReference: IWeekItem
override fun getWeekReference() = weekReference
override fun setWeekReference(value: IWeekItem) {
weekReference = value
}
override fun getShowBadge() = showBadge
override fun setShowBadge(value: Boolean) {
showBadge = value
showItemBadge = value
}
override fun getId() = id
override fun getStartTime() = time
override fun getEndTime() = time
override fun getTitle() = ""
override fun getDescription() = ""
override fun getLocation() = ""
override fun getColor() = color
override fun getTextColor() = 0
override fun isPlaceholder() = false
override fun isAllDay() = false
override fun setId(value: Long) = Unit
override fun setStartTime(value: Calendar) = Unit
override fun setEndTime(value: Calendar) = Unit
override fun setTitle(value: String) = Unit
override fun setDescription(value: String) = Unit
override fun setLocation(value: String) = Unit
override fun setTextColor(value: Int) = Unit
override fun setPlaceholder(value: Boolean) = Unit
override fun setAllDay(value: Boolean) = Unit
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
class AgendaEvent(
val event: EventFull,
showBadge: Boolean = !event.seen
) : BaseEvent(
id = event.id,
time = event.startTimeCalendar,
color = event.eventColor,
showBadge = showBadge
) {
override fun copy() = AgendaEvent(event, showBadge)
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class AgendaEventGroup(
val profileId: Int,
val date: Date,
val typeId: Long,
val typeName: String,
val typeColor: Int,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = typeColor,
showBadge = showBadge
) {
override fun copy() = AgendaEventGroup(profileId, date, typeId, typeName, typeColor, count, showBadge)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedGroupBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class AgendaEventGroupRenderer : EventRenderer<AgendaEventGroup>() {
override fun render(view: View, event: AgendaEventGroup) {
val b = AgendaWrappedGroupBinding.bind(view).item
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.text = event.typeName
b.name.setTextColor(Colors.legibleTextColor(event.color))
b.count.text = event.count.toString()
b.count.background.setTintColor(android.R.attr.colorBackground.resolveAttr(view.context))
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_group
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.annotation.SuppressLint
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import com.mikepenz.iconics.view.IconicsTextView
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding
import pl.szczodrzynski.edziennik.join
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.managers.EventManager
class AgendaEventRenderer(
val manager: EventManager,
val isCompact: Boolean
) : EventRenderer<AgendaEvent>() {
@SuppressLint("SetTextI18n")
override fun render(view: View, aEvent: AgendaEvent) {
if (isCompact) {
val b = AgendaWrappedEventCompactBinding.bind(view).item
bindView(aEvent, b.card, b.title, null, b.badgeBackground, b.badge)
} else {
val b = AgendaWrappedEventBinding.bind(view).item
bindView(aEvent, b.card, b.title, b.subtitle, b.badgeBackground, b.badge)
}
}
private fun bindView(
aEvent: AgendaEvent,
card: FrameLayout,
title: IconicsTextView,
subtitle: TextView?,
badgeBackground: View,
badge: View
) {
val event = aEvent.event
val textColor = Colors.legibleTextColor(event.eventColor)
val timeText = if (event.time == null)
card.context.getString(R.string.agenda_event_all_day)
else
event.time!!.stringHM
val eventSubtitle = listOfNotNull(
timeText,
event.subjectLongName,
event.teacherName,
event.teamName
).join(", ")
card.foreground.setTintColor(event.eventColor)
card.background.setTintColor(event.eventColor)
manager.setEventTopic(title, event, doneIconColor = textColor)
title.setTextColor(textColor)
subtitle?.text = eventSubtitle
subtitle?.setTextColor(textColor)
badgeBackground.isVisible = aEvent.showItemBadge
badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(card.context)
)
badge.isVisible = aEvent.showItemBadge
}
override fun getEventLayout() = if (isCompact)
R.layout.agenda_wrapped_event_compact
else
R.layout.agenda_wrapped_event
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class LessonChangeCounter(
val lessonChangeDate: Date,
var lessonChangeCount: Int
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,243 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange;
import com.github.tibolte.agendacalendarview.models.CalendarEvent;
import com.github.tibolte.agendacalendarview.models.IDayItem;
import com.github.tibolte.agendacalendarview.models.IWeekItem;
import java.util.Calendar;
import pl.szczodrzynski.edziennik.utils.models.Date;
public class LessonChangeEvent implements CalendarEvent {
/**
* Id of the event.
*/
private long mId;
/**
* Color to be displayed in the agenda view.
*/
private int mColor;
/**
* Text color displayed on the background color
*/
private int mTextColor;
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private Calendar mInstanceDay;
/**
* Start time of the event.
*/
private Calendar mStartTime;
/**
* End time of the event.
*/
private Calendar mEndTime;
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IDayItem mDayReference;
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IWeekItem mWeekReference;
private int profileId;
private Date lessonChangeDate;
private int lessonChangeCount;
public LessonChangeEvent(LessonChangeEvent calendarEvent) {
this.mId = calendarEvent.getId();
this.mColor = calendarEvent.getColor();
this.mTextColor = calendarEvent.getTextColor();
this.mStartTime = calendarEvent.getStartTime();
this.mEndTime = calendarEvent.getEndTime();
this.profileId = calendarEvent.getProfileId();
this.lessonChangeDate = calendarEvent.getLessonChangeDate();
this.lessonChangeCount = calendarEvent.getLessonChangeCount();
}
public LessonChangeEvent(long mId, int mColor, int mTextColor, Calendar mStartTime, Calendar mEndTime, int profileId, Date lessonChangeDate, int lessonChangeCount) {
this.mId = mId;
this.mColor = mColor;
this.mTextColor = mTextColor;
this.mStartTime = mStartTime;
this.mEndTime = mEndTime;
this.profileId = profileId;
this.lessonChangeDate = lessonChangeDate;
this.lessonChangeCount = lessonChangeCount;
}
public int getProfileId() {
return profileId;
}
public Date getLessonChangeDate() {
return lessonChangeDate;
}
public int getLessonChangeCount() {
return lessonChangeCount;
}
public void setProfileId(int profileId) {
this.profileId = profileId;
}
public void setLessonChangeDate(Date lessonChangeDate) {
this.lessonChangeDate = lessonChangeDate;
}
public void setLessonChangeCount(int lessonChangeCount) {
this.lessonChangeCount = lessonChangeCount;
}
@Override
public void setPlaceholder(boolean placeholder) {
}
@Override
public boolean isPlaceholder() {
return false;
}
@Override
public String getLocation() {
return null;
}
@Override
public void setLocation(String mLocation) {
}
@Override
public long getId() {
return mId;
}
@Override
public void setId(long mId) {
this.mId = mId;
}
@Override
public boolean getShowBadge() {
return false;
}
@Override
public void setShowBadge(boolean mShowBadge) {
}
@Override
public int getTextColor() {
return mTextColor;
}
@Override
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
}
@Override
public String getDescription() {
return null;
}
@Override
public void setDescription(String mDescription) {
}
@Override
public boolean isAllDay() {
return false;
}
@Override
public void setAllDay(boolean allDay) {
}
@Override
public Calendar getStartTime() {
return mStartTime;
}
@Override
public void setStartTime(Calendar mStartTime) {
this.mStartTime = mStartTime;
}
@Override
public Calendar getEndTime() {
return mEndTime;
}
@Override
public void setEndTime(Calendar mEndTime) {
this.mEndTime = mEndTime;
}
@Override
public String getTitle() {
return null;
}
@Override
public void setTitle(String mTitle) {
}
@Override
public Calendar getInstanceDay() {
return mInstanceDay;
}
@Override
public void setInstanceDay(Calendar mInstanceDay) {
this.mInstanceDay = mInstanceDay;
this.mInstanceDay.set(Calendar.HOUR, 0);
this.mInstanceDay.set(Calendar.MINUTE, 0);
this.mInstanceDay.set(Calendar.SECOND, 0);
this.mInstanceDay.set(Calendar.MILLISECOND, 0);
this.mInstanceDay.set(Calendar.AM_PM, 0);
}
@Override
public IDayItem getDayReference() {
return mDayReference;
}
@Override
public void setDayReference(IDayItem mDayReference) {
this.mDayReference = mDayReference;
}
@Override
public IWeekItem getWeekReference() {
return mWeekReference;
}
@Override
public void setWeekReference(IWeekItem mWeekReference) {
this.mWeekReference = mWeekReference;
}
@Override
public CalendarEvent copy() {
return new LessonChangeEvent(this);
}
@Override
public int getColor() {
return mColor;
}
}

View File

@ -1,21 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import android.view.View
import android.widget.TextView
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
class LessonChangeEventRenderer : EventRenderer<LessonChangeEvent>() {
override fun render(view: View?, event: LessonChangeEvent) {
val card = view?.findViewById<CardView>(R.id.lesson_change_card)
val changeText = view?.findViewById<TextView>(R.id.lesson_change_text)
val changeCount = view?.findViewById<TextView>(R.id.lessonChangeCount)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor)
changeCount?.setTextColor(event.textColor)
changeCount?.text = event.lessonChangeCount.toString()
}
override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class LessonChangesEvent(
val profileId: Int,
val date: Date,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = 0xff78909c.toInt(),
showBadge = false,
showItemBadge = showBadge
) {
override fun copy() = LessonChangesEvent(profileId, date, count, showItemBadge)
override fun getShowBadge() = false
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
override fun render(view: View, event: LessonChangesEvent) {
val b = AgendaWrappedCounterBinding.bind(view).item
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(view.context)
)
b.badge.isVisible = event.showItemBadge
}
fun render(b: AgendaCounterItemBinding, event: LessonChangesEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(b.root.context)
)
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceCounter (
val teacherAbsenceDate: Date,
var teacherAbsenceCount: Int = 0
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,188 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceEvent : CalendarEvent {
/**
* Id of the event.
*/
private var mId: Long = 0
/**
* Color to be displayed in the agenda view.
*/
private var mColor: Int = 0
/**
* Text color displayed on the background color
*/
private var mTextColor: Int = 0
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private var mInstanceDay: Calendar? = null
/**
* Start time of the event.
*/
private var mStartTime: Calendar? = null
/**
* End time of the event.
*/
private var mEndTime: Calendar? = null
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mDayReference: IDayItem? = null
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mWeekReference: IWeekItem? = null
private var profileId: Int = 0
var teacherAbsenceDate: Date? = null
var teacherAbsenceCount: Int = 0
constructor(calendarEvent: TeacherAbsenceEvent) {
this.mId = calendarEvent.id
this.mColor = calendarEvent.color
this.mTextColor = calendarEvent.textColor
this.mStartTime = calendarEvent.startTime
this.mEndTime = calendarEvent.endTime
this.profileId = calendarEvent.profileId
this.teacherAbsenceDate = calendarEvent.teacherAbsenceDate
this.teacherAbsenceCount = calendarEvent.teacherAbsenceCount
}
constructor(mId: Long, mColor: Int, mTextColor: Int, mStartTime: Calendar, mEndTime: Calendar, profileId: Int, teacherAbsenceDate: Date, teacherAbsenceCount: Int) {
this.mId = mId
this.mColor = mColor
this.mTextColor = mTextColor
this.mStartTime = mStartTime
this.mEndTime = mEndTime
this.profileId = profileId
this.teacherAbsenceDate = teacherAbsenceDate
this.teacherAbsenceCount = teacherAbsenceCount
}
override fun setPlaceholder(placeholder: Boolean) {
}
override fun isPlaceholder(): Boolean {
return false
}
override fun getLocation(): String? {
return null
}
override fun setLocation(mLocation: String) {
}
override fun getId(): Long {
return mId
}
override fun setId(mId: Long) {
this.mId = mId
}
override fun getShowBadge(): Boolean {
return false
}
override fun setShowBadge(mShowBadge: Boolean) {
}
override fun getTextColor(): Int {
return mTextColor
}
override fun setTextColor(mTextColor: Int) {
this.mTextColor = mTextColor
}
override fun getDescription(): String? {
return null
}
override fun setDescription(mDescription: String) {
}
override fun isAllDay(): Boolean {
return false
}
override fun setAllDay(allDay: Boolean) {
}
override fun getStartTime(): Calendar? {
return mStartTime
}
override fun setStartTime(mStartTime: Calendar) {
this.mStartTime = mStartTime
}
override fun getEndTime(): Calendar? {
return mEndTime
}
override fun setEndTime(mEndTime: Calendar) {
this.mEndTime = mEndTime
}
override fun getTitle(): String? {
return null
}
override fun setTitle(mTitle: String) {
}
override fun getInstanceDay(): Calendar? {
return mInstanceDay
}
override fun setInstanceDay(mInstanceDay: Calendar) {
this.mInstanceDay = mInstanceDay
this.mInstanceDay!!.set(Calendar.HOUR, 0)
this.mInstanceDay!!.set(Calendar.MINUTE, 0)
this.mInstanceDay!!.set(Calendar.SECOND, 0)
this.mInstanceDay!!.set(Calendar.MILLISECOND, 0)
this.mInstanceDay!!.set(Calendar.AM_PM, 0)
}
override fun getDayReference(): IDayItem? {
return mDayReference
}
override fun setDayReference(mDayReference: IDayItem) {
this.mDayReference = mDayReference
}
override fun getWeekReference(): IWeekItem? {
return mWeekReference
}
override fun setWeekReference(mWeekReference: IWeekItem) {
this.mWeekReference = mWeekReference
}
override fun copy(): CalendarEvent {
return TeacherAbsenceEvent(this)
}
override fun getColor(): Int {
return mColor
}
class TeacherAbsenceEvent(
val profileId: Int,
val date: Date,
val count: Int
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = 0xffff1744.toInt(),
showBadge = false
) {
override fun copy() = TeacherAbsenceEvent(profileId, date, count)
}

View File

@ -1,21 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import android.view.View
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
override fun render(view: View?, event: TeacherAbsenceEvent) {
val card = view?.findViewById<CardView>(R.id.teacherAbsenceCard)
val changeText = view?.findViewById<TextView>(R.id.teacherAbsenceText)
val changeCount = view?.findViewById<TextView>(R.id.teacherAbsenceCount)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor)
changeCount?.setTextColor(event.textColor)
changeCount?.text = event.teacherAbsenceCount.toString()
override fun render(view: View, event: TeacherAbsenceEvent) {
val b = AgendaWrappedCounterBinding.bind(view).item
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
}
override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence
fun render(b: AgendaCounterItemBinding, event: TeacherAbsenceEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
}

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.BetterLink
import kotlin.coroutines.CoroutineContext
class AttendanceDetailsDialog(
@ -60,5 +61,13 @@ class AttendanceDetailsDialog(
b.attendanceName.background.setTintColor(attendanceColor)
b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no)
attendance.teacherName?.let { name ->
BetterLink.attach(
b.teacherName,
teachers = mapOf(attendance.teacherId to name),
onActionSelected = dialog::dismiss
)
}
}}
}

View File

@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main
// local/private variables go here
private val manager by lazy { app.attendanceManager }
private val manager
get() = app.attendanceManager
private var viewType = AttendanceFragment.VIEW_DAYS
private var expandSubjectId = 0L

View File

@ -47,7 +47,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main
// local/private variables go here
private val manager by lazy { app.attendanceManager }
private val manager
get() = app.attendanceManager
private var expandSubjectId = 0L
private var attendance = listOf<AttendanceFull>()

View File

@ -9,7 +9,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
class FragmentLazyPagerAdapter(
fragmentManager: FragmentManager,
swipeRefreshLayout: SwipeRefreshLayout,
swipeRefreshLayout: SwipeRefreshLayout? = null,
val fragments: List<Pair<LazyFragment, CharSequence>>
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
override fun getPage(position: Int) = fragments[position].first

View File

@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.Utils.bs
import pl.szczodrzynski.edziennik.utils.models.Date
@ -83,6 +84,14 @@ class NoticesAdapter//getting the context and product list with constructor
} else {
holder.noticesItemReason.background = null
}
BetterLink.attach(holder.noticesItemReason)
notice.teacherName?.let { name ->
BetterLink.attach(holder.noticesItemTeacherName, teachers = mapOf(
notice.teacherId to name
))
}
}
override fun getItemCount(): Int {

View File

@ -5,10 +5,12 @@
package pl.szczodrzynski.edziennik.ui.modules.debug
import android.os.Bundle
import android.os.Process
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.sqlite.db.SimpleSQLiteQuery
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -21,6 +23,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.fslogin.decode
import kotlin.coroutines.CoroutineContext
import kotlin.system.exitProcess
class LabPageFragment : LazyFragment(), CoroutineScope {
companion object {
@ -75,6 +78,37 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
}
b.chucker.isChecked = app.config.enableChucker
b.chucker.onChange { _, isChecked ->
app.config.enableChucker = isChecked
MaterialAlertDialogBuilder(activity)
.setTitle("Restart")
.setMessage("Wymagany restart aplikacji")
.setPositiveButton(R.string.ok) { _, _ ->
Process.killProcess(Process.myPid())
Runtime.getRuntime().exit(0)
exitProcess(0)
}
.setCancelable(false)
.show()
}
b.disableDebug.onClick {
app.config.debugMode = false
MaterialAlertDialogBuilder(activity)
.setTitle("Restart")
.setMessage("Wymagany restart aplikacji")
.setPositiveButton(R.string.ok) { _, _ ->
Process.killProcess(Process.myPid())
Runtime.getRuntime().exit(0)
exitProcess(0)
}
.setCancelable(false)
.show()
}
b.unarchive.onClick {
app.profile.archived = false
app.profile.archiveId = null

View File

@ -166,7 +166,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
json.add("App.profile", app.gson.toJsonTree(app.profile))
json.add("App.profile.studentData", app.profile.studentData)
json.add("App.profile.loginStore", loginStore?.data ?: JsonObject())
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values)))
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values.toSortedMap())))
}
adapter.items = LabJsonAdapter.expand(json, 0)
adapter.notifyDataSetChanged()

View File

@ -40,7 +40,8 @@ class GradesAdapter(
}
private val app = activity.applicationContext as App
private val manager = app.gradesManager
private val manager
get() = app.gradesManager
private val job = Job()
override val coroutineContext: CoroutineContext

View File

@ -48,9 +48,12 @@ class GradesListFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main
// local/private variables go here
private val manager by lazy { app.gradesManager }
private val dontCountEnabled by lazy { manager.dontCountEnabled }
private val dontCountGrades by lazy { manager.dontCountGrades }
private val manager
get() = app.gradesManager
private val dontCountEnabled
get() = manager.dontCountEnabled
private val dontCountGrades
get() = manager.dontCountGrades
private var expandSubjectId = 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

View File

@ -95,8 +95,10 @@ class LoginEggsFragment : Fragment(), CoroutineScope {
anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 10
anim.fillAfter = true
activity.getRootView().startAnimation(anim)
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
activity.runOnUiThread {
activity.getRootView().startAnimation(anim)
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
}
}
}, "EggInterface")
loadUrl("https://szkolny.eu/game/runner.html")

View File

@ -85,6 +85,9 @@ class LoginFormFragment : Fragment(), CoroutineScope {
if (credential is LoginInfo.FormField) {
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
b.textLayout.hint = app.getString(credential.name)
if (credential.isNumber) {
b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER
}
if (credential.hideText) {
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE

View File

@ -179,6 +179,7 @@ object LoginInfo {
ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING to R.string.error_312_reason
),
isRequired = true,
isNumber = true,
validationRegex = "[0-9]+",
caseMode = FormField.CaseMode.LOWER_CASE
)
@ -401,6 +402,7 @@ object LoginInfo {
val validationRegex: String,
val caseMode: CaseMode = CaseMode.UNCHANGED,
val hideText: Boolean = false,
val isNumber: Boolean = false,
val stripTextRegex: String? = null
) : BaseCredential(keyName, name, errorCodes) {
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }

View File

@ -76,7 +76,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
val maxProfileId = max(
app.db.profileDao().lastId ?: 0,
activity.profiles.maxBy { it.profile.id }?.profile?.id ?: 0
activity.profiles.maxByOrNull { it.profile.id }?.profile?.id ?: 0
)
val loginType = args.getInt("loginType", -1)
val loginMode = args.getInt("loginMode", 0)

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