Compare commits

..

67 Commits
v4.7.1 ... 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
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
180 changed files with 4139 additions and 1672 deletions

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -1,7 +1,13 @@
<h3>Wersja 4.7.1, 2021-04-12</h3> <h3>Wersja 4.9, 2021-09-11</h3>
<ul> <ul>
<li>Poprawiono sprawdzanie dostępności e-dziennika.</li> <li>Vulcan: naprawiono brakujące lekcje w planie. @Antoni-Czaplicki</li>
<li>Zmieniono datę w informacjach o aplikacji. @Luncenok</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> </ul>
<br> <br>
<br> <br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0xcc, 0x64, 0xdb, 0x3a, 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); 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 val profileId
get() = profile.id get() = profile.id
var enableChucker = false
var debugMode = false var debugMode = false
var devMode = false var devMode = false
} }
@ -115,9 +116,11 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
HyperLog.initialize(this) HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE) HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this)) HyperLog.setLogFormat(DebugLogFormat(this))
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR) if (enableChucker) {
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector) val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
builder.addInterceptor(chuckerInterceptor) val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
} }
http = builder.build() http = builder.build()
@ -172,6 +175,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
App.profile = Profile(0, 0, 0, "") App.profile = Profile(0, 0, 0, "")
debugMode = BuildConfig.DEBUG debugMode = BuildConfig.DEBUG
devMode = config.debugMode || debugMode devMode = config.debugMode || debugMode
enableChucker = config.enableChucker || devMode
if (!profileLoadById(config.lastProfileId)) { if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) } db.profileDao().firstId?.let { profileLoadById(it) }

View File

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

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.Observer
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.danimahardhika.cafebar.CafeBar import com.danimahardhika.cafebar.CafeBar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jetradarmobile.snowfall.SnowfallView
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt 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.settings.SettingsFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.navlib.* import pl.szczodrzynski.navlib.*
@ -470,9 +468,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// IT'S WINTER MY DUDES // IT'S WINTER MY DUDES
val today = Date.getToday() 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)) 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 // WHAT'S NEW DIALOG
if (app.config.appVersion < BuildConfig.VERSION_CODE) { 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 } get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
set(value) { set("debugMode", value); mDebugMode = value } 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 private var mDevModePassword: String? = null
var devModePassword: String? var devModePassword: String?
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword } get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }

View File

@ -49,6 +49,11 @@ class ConfigUI(private val config: Config) {
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
set(value) { config.set("snowfall", value); mSnowfall = value } 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 private var mBottomSheetOpened: Boolean? = null
var bottomSheetOpened: Boolean var bottomSheetOpened: Boolean
get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } 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 } get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value } 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 private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel> var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value } 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" 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 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_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_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" 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_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE=" const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398" const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49" const val LIBRUS_API_CLIENT_ID_JST = "59"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42" //const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21" const val LIBRUS_JST_DEMO_CODE = "68656A21"

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_CERTIFICATE_GONE = 363
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364 const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365 const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
const val ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY = 366
const val ERROR_VULCAN_API_DEPRECATED = 390 const val ERROR_VULCAN_API_DEPRECATED = 390
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501 const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501

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 { val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL) """<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.net.URLEncoder
class MobidziennikWebMessagesAll(override val data: DataMobidziennik, class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
override val lastSync: Long?, override val lastSync: Long?,
@ -27,7 +28,8 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
} }
init { 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) MobidziennikLuckyNumberExtractor(data, text)
val doc = Jsoup.parse(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? val loginShort: String?
get() = studentLogin?.split('@')?.get(0) get() = studentLogin?.split('@')?.get(0)
fun getSubject(name: String): Subject {
val id = name.crc32()
return subjectList.singleOrNull { it.id == id } ?: run {
val subject = Subject(profileId, id, name, name)
subjectList.put(id, subject)
subject
}
}
fun getTeacher(firstName: String, lastName: String): Teacher {
val name = "$firstName $lastName".fixName()
return teacherList.singleOrNull { it.fullName == name } ?: run {
val id = name.crc32()
val teacher = Teacher(profileId, id, firstName, lastName)
teacherList.put(id, teacher)
teacher
}
}
fun getTeam(name: String? = null): Team {
if (name == "cała klasa" || name == null) return teamClass ?: run {
val id = className!!.crc32()
val teamCode = "$schoolShortName:$className"
val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1)
teamList.put(id, team)
return team
} else {
val id = name.crc32()
val teamCode = "$schoolShortName:$name"
val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1)
teamList.put(id, team)
return team
}
}
} }

View File

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

View File

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

View File

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

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) } val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
?: return@forEach ?: return@forEach
val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach
val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) } val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(null, it) }
?: return@forEach ?: return@forEach
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
val teacher = data.getTeacher(teacherFirstName, teacherLastName) val teacher = data.getTeacher(teacherFirstName, teacherLastName)
val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach val team = lesson.getString("Group")?.let {
data.getTeam(
id = null,
name = it,
schoolCode = data.schoolShortName ?: "",
isTeamClass = it == "cała klasa"
)
} ?: return@forEach
val classroom = lesson.getString("Room") val classroom = lesson.getString("Room")
Lesson(data.profileId, -1).also { Lesson(data.profileId, -1).also {

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 } get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value } set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
private var mSenderAddressHash: String? = null
var senderAddressHash: String?
get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash }
set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value }
private var mSenderAddressName: String? = null
var senderAddressName: String?
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName }
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value }
val apiUrl: String? val apiUrl: String?
get() { get() {
val url = when (apiToken[symbol]?.substring(0, 3)) { val url = when (apiToken[symbol]?.substring(0, 3)) {

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/ /*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDXHhAtZBW===.$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.data.db.entity.*
import pl.szczodrzynski.edziennik.getNotificationTitle import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date 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>) { class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
companion object { companion object {
@ -42,13 +43,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
val text = app.getString( val text = app.getString(
R.string.notification_lesson_change_format, R.string.notification_lesson_change_format,
lesson.getDisplayChangeType(app), lesson.getDisplayChangeType(app),
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, lesson.displayDate?.formattedString ?: "",
lesson.changeSubjectName 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( notifications += Notification(
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id), id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE), title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE, type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
profileId = lesson.profileId, profileId = lesson.profileId,
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, 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.date.formattedString,
event.subjectLongName 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 val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, 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.date.formattedString,
event.topic 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 val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
@ -130,10 +161,20 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
gradeName, gradeName,
grade.subjectLongName 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( notifications += Notification(
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id), id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE), title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_GRADE, type = Notification.TYPE_NEW_GRADE,
profileId = grade.profileId, profileId = grade.profileId,
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name, profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
@ -158,10 +199,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
notice.teacherName, notice.teacherName,
Date.fromMillis(notice.addedDate).formattedString Date.fromMillis(notice.addedDate).formattedString
) )
val textLong = app.getString(
R.string.notification_notice_long_format,
noticeTypeStr,
notice.teacherName ?: "-",
notice.text.take(200)
)
notifications += Notification( notifications += Notification(
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id), id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE), title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_NOTICE, type = Notification.TYPE_NEW_NOTICE,
profileId = notice.profileId, profileId = notice.profileId,
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name, profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
@ -193,10 +241,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
attendance.subjectLongName, attendance.subjectLongName,
attendance.date.formattedString 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( notifications += Notification(
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE), title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_ATTENDANCE, type = Notification.TYPE_NEW_ATTENDANCE,
profileId = attendance.profileId, profileId = attendance.profileId,
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name, 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.app.NotificationCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.core.util.set 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.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -107,6 +110,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts)) .setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText) .setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.InboxStyle() .setStyle(NotificationCompat.InboxStyle()
.also { .also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 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) .setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
.setTicker("${it.profileName}: ${it.title}") .setTicker("${it.profileName}: ${it.title}")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply {
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.BigTextStyle() .setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text)) .bigText(it.textLong ?: it.text))
.setWhen(it.addedDate) .setWhen(it.addedDate)
.addDefaults() .addDefaults()
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
@ -160,6 +170,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts)) .setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText) .setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.addDefaults() .addDefaults()
.setGroupSummary(true) .setGroupSummary(true)
.setContentIntent(summaryIntent) .setContentIntent(summaryIntent)

View File

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

View File

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

View File

@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
"LIMIT 1") "LIMIT 1")
fun getBetweenDates(dateFrom: Date, dateTo: Date) = 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") 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 // GET ALL - NOW
fun getAllNow(profileId: Int) = fun getAllNow(profileId: Int) =

View File

@ -45,6 +45,7 @@ open class Event(
var addedDate: Long = System.currentTimeMillis() var addedDate: Long = System.currentTimeMillis()
) : Keepable() { ) : Keepable() {
companion object { companion object {
const val TYPE_ELEARNING = -5L
const val TYPE_UNDEFINED = -2L const val TYPE_UNDEFINED = -2L
const val TYPE_HOMEWORK = -1L const val TYPE_HOMEWORK = -1L
const val TYPE_DEFAULT = 0L const val TYPE_DEFAULT = 0L
@ -57,7 +58,7 @@ open class Event(
const val TYPE_READING = 7L const val TYPE_READING = 7L
const val TYPE_CLASS_EVENT = 8L const val TYPE_CLASS_EVENT = 8L
const val TYPE_INFORMATION = 9L 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_HOMEWORK = 0xff795548.toInt()
const val COLOR_DEFAULT = 0xffffc107.toInt() const val COLOR_DEFAULT = 0xffffc107.toInt()
const val COLOR_EXAM = 0xfff44336.toInt() const val COLOR_EXAM = 0xfff44336.toInt()
@ -69,7 +70,6 @@ open class Event(
const val COLOR_READING = 0xFFFFEB3B.toInt() const val COLOR_READING = 0xFFFFEB3B.toInt()
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt() const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
const val COLOR_INFORMATION = 0xff039be5.toInt() const val COLOR_INFORMATION = 0xff039be5.toInt()
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
} }
@ColumnInfo(name = "eventAddedManually") @ColumnInfo(name = "eventAddedManually")
@ -116,14 +116,7 @@ open class Event(
var showAsUnseen: Boolean? = null var showAsUnseen: Boolean? = null
val startTimeCalendar: Calendar val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set( get() = date.getAsCalendar(time)
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
val endTimeCalendar: Calendar val endTimeCalendar: Calendar
get() = startTimeCalendar.also { 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.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.google.gson.JsonObject 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 import pl.szczodrzynski.edziennik.MainActivity
@Entity(tableName = "notifications") @Entity(tableName = "notifications")
@ -19,6 +21,7 @@ data class Notification(
val title: String, val title: String,
val text: String, val text: String,
val textLong: String? = null,
val type: Int, val type: Int,
@ -96,4 +99,19 @@ data class Notification(
fillIntent(intent) fillIntent(intent)
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT) 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 val isParent
get() = accountName != null get() = accountName != null
val accountOwnerName
get() = accountName ?: studentNameLong
val registerName val registerName
get() = when (loginStoreType) { get() = when (loginStoreType) {
LOGIN_TYPE_LIBRUS -> "librus" LOGIN_TYPE_LIBRUS -> "librus"

View File

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

View File

@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext
class EventDetailsDialog( class EventDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val event: EventFull, var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -46,6 +46,8 @@ class EventDetailsDialog(
private var removeEventDialog: AlertDialog? = null private var removeEventDialog: AlertDialog? = null
private val eventShared = event.sharedBy != null private val eventShared = event.sharedBy != null
private val eventOwn = event.sharedBy == "self" private val eventOwn = event.sharedBy == "self"
private val manager
get() = app.eventManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -92,6 +94,10 @@ class EventDetailsDialog(
b.eventShared = eventShared b.eventShared = eventShared
b.eventOwn = eventOwn b.eventOwn = eventOwn
if (!event.seen) {
manager.markAsSeen(event)
}
val bullet = "" val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
@ -100,6 +106,8 @@ class EventDetailsDialog(
} }
catch (_: Exception) {} catch (_: Exception) {}
manager.setLegendText(b.legend, event)
b.typeColor.background?.setTintColor(event.eventColor) b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf( b.details = mutableListOf(
@ -135,6 +143,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
b.checkDoneButton.isChecked = true b.checkDoneButton.isChecked = true
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -145,6 +154,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
} }
} }
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done) b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
@ -156,6 +166,14 @@ class EventDetailsDialog(
activity, activity,
event.profileId, event.profileId,
editingEvent = event, editingEvent = event,
onSaveListener = {
if (it == null) {
dialog.dismiss()
return@EventManualDialog
}
event = it
update()
},
onShowListener = onShowListener, onShowListener = onShowListener,
onDismissListener = onDismissListener onDismissListener = onDismissListener
) )
@ -323,8 +341,6 @@ class EventDetailsDialog(
removeEventDialog?.dismiss() removeEventDialog?.dismiss()
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() 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 { private fun openInCalendar() { launch {

View File

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

View File

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

View File

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

View File

@ -50,7 +50,8 @@ class LessonDetailsDialog(
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter private lateinit var adapter: EventListAdapter
private val manager by lazy { app.timetableManager } private val manager
get() = app.timetableManager
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)

View File

@ -12,10 +12,6 @@ import android.widget.Toast
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.applandeo.materialcalendarview.EventDay 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.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
@ -25,21 +21,14 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding 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.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog 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.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -59,7 +48,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private var type: Int = Profile.AGENDA_DEFAULT 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? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (getActivity() == null || context == null) return null if (getActivity() == null || context == null) return null
@ -82,38 +72,61 @@ class AgendaFragment : Fragment(), CoroutineScope {
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener {
activity.bottomSheet.close() 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) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view) .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) .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() 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 app.config.forProfile().ui.agendaViewType = type
activity.reloadTarget() activity.reloadTarget()
}), },
BottomSheetSeparatorItem(true), BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener { launch { .withOnClickListener {
activity.bottomSheet.close() launch {
withContext(Dispatchers.Default) { activity.bottomSheet.close()
App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) 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.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.add) activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener {
EventManualDialog(activity, app.profileId, defaultDate = actualDate) EventManualDialog(
}) activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
}
activity.gainAttention() activity.gainAttention()
activity.gainAttentionFAB() 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 { private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch {
if (!isAdded) if (!isAdded)
return@launch return@launch
checkEventTypes()
delay(500) delay(500)
val eventList = mutableListOf<CalendarEvent>() agendaDefault = AgendaFragmentDefault(activity, app, b)
agendaDefault?.initView(this@AgendaFragment)
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
}}} }}}
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
checkEventTypes()
delay(300) delay(300)
val dayList = mutableListOf<EventDay>() val dayList = mutableListOf<EventDay>()

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 package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import com.github.tibolte.agendacalendarview.models.CalendarEvent import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceEvent : CalendarEvent { class TeacherAbsenceEvent(
/** val profileId: Int,
* Id of the event. val date: Date,
*/ val count: Int
private var mId: Long = 0 ) : BaseEvent(
/** id = date.value.toLong(),
* Color to be displayed in the agenda view. time = date.asCalendar,
*/ color = 0xffff1744.toInt(),
private var mColor: Int = 0 showBadge = false
/** ) {
* Text color displayed on the background color override fun copy() = TeacherAbsenceEvent(profileId, date, count)
*/
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
}
} }

View File

@ -1,21 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import android.view.View import android.view.View
import android.widget.TextView import androidx.core.view.isVisible
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R 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>() { class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
override fun render(view: View?, event: TeacherAbsenceEvent) {
val card = view?.findViewById<CardView>(R.id.teacherAbsenceCard) override fun render(view: View, event: TeacherAbsenceEvent) {
val changeText = view?.findViewById<TextView>(R.id.teacherAbsenceText) val b = AgendaWrappedCounterBinding.bind(view).item
val changeCount = view?.findViewById<TextView>(R.id.teacherAbsenceCount) val textColor = Colors.legibleTextColor(event.color)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor) b.card.foreground.setTintColor(event.color)
changeCount?.setTextColor(event.textColor) b.card.background.setTintColor(event.color)
changeCount?.text = event.teacherAbsenceCount.toString() 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

@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // 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 viewType = AttendanceFragment.VIEW_DAYS
private var expandSubjectId = 0L private var expandSubjectId = 0L

View File

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

View File

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

View File

@ -5,10 +5,12 @@
package pl.szczodrzynski.edziennik.ui.modules.debug package pl.szczodrzynski.edziennik.ui.modules.debug
import android.os.Bundle import android.os.Bundle
import android.os.Process
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -21,6 +23,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.fslogin.decode import pl.szczodrzynski.fslogin.decode
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.system.exitProcess
class LabPageFragment : LazyFragment(), CoroutineScope { class LabPageFragment : LazyFragment(), CoroutineScope {
companion object { companion object {
@ -75,6 +78,37 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
} }
b.chucker.isChecked = app.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 { b.unarchive.onClick {
app.profile.archived = false app.profile.archived = false
app.profile.archiveId = null 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", app.gson.toJsonTree(app.profile))
json.add("App.profile.studentData", app.profile.studentData) json.add("App.profile.studentData", app.profile.studentData)
json.add("App.profile.loginStore", loginStore?.data ?: JsonObject()) json.add("App.profile.loginStore", loginStore?.data ?: JsonObject())
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values))) json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values.toSortedMap())))
} }
adapter.items = LabJsonAdapter.expand(json, 0) adapter.items = LabJsonAdapter.expand(json, 0)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,10 +30,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.colorAttr import pl.szczodrzynski.navlib.colorAttr
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
@ -64,10 +66,20 @@ class MessageFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return if (!isAdded) return
activity.bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
b.closeButton.setImageDrawable( b.closeButton.setImageDrawable(
IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply { IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply {
colorAttr(activity, android.R.attr.textColorSecondary) colorAttr(activity, android.R.attr.textColorSecondary)
sizeDp = 16 sizeDp = 24
} }
) )
b.closeButton.setOnClickListener { activity.navigateUp() } b.closeButton.setOnClickListener { activity.navigateUp() }

View File

@ -1,11 +1,8 @@
package pl.szczodrzynski.edziennik.ui.modules.messages package pl.szczodrzynski.edziennik.ui.modules.messages
import android.graphics.Typeface import android.graphics.Typeface
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable import android.widget.Filterable
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -13,22 +10,19 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.cleanDiacritics
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.MessagesFilter
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder
import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min
class MessagesAdapter( class MessagesAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val teachers: List<Teacher>, val teachers: List<Teacher>,
val onItemClick: ((item: MessageFull) -> Unit)? = null val onItemClick: ((item: MessageFull) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
companion object { companion object {
private const val TAG = "MessagesAdapter" private const val TAG = "MessagesAdapter"
@ -43,41 +37,10 @@ class MessagesAdapter(
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
var items = mutableListOf<Any>() var items = listOf<Any>()
var allItems = mutableListOf<Any>() var allItems = listOf<Any>()
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) } val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) } val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
private val comparator by lazy { Comparator { o1: Any, o2: Any ->
if (o1 !is MessageFull || o2 !is MessageFull)
return@Comparator 0
when {
// standard sorting
o1.filterWeight > o2.filterWeight -> return@Comparator 1
o1.filterWeight < o2.filterWeight -> return@Comparator -1
else -> when {
// reversed sorting
o1.addedDate > o2.addedDate -> return@Comparator -1
o1.addedDate < o2.addedDate -> return@Comparator 1
else -> return@Comparator 0
}
}
}}
val textWatcher by lazy {
object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
getFilter().filter(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
/*items.getOrNull(0)?.let {
if (it is MessagesSearch) {
it.searchText = s?.toString() ?: ""
}
}*/
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
@ -103,138 +66,16 @@ class MessagesAdapter(
return return
when { when {
holder is MessageViewHolder && item is MessageFull -> holder.onBind(activity, app, item, position, this) holder is MessageViewHolder
holder is SearchViewHolder && item is MessagesSearch -> holder.onBind(activity, app, item, position, this) && item is MessageFull -> holder.onBind(activity, app, item, position, this)
holder is SearchViewHolder
&& item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
} }
} }
private val messagesFilter by lazy {
MessagesFilter(this)
}
override fun getItemCount() = items.size override fun getItemCount() = items.size
override fun getFilter() = filter override fun getFilter() = messagesFilter
private var prevCount = -1
private val filter by lazy { object : Filter() {
override fun performFiltering(prefix: CharSequence?): FilterResults {
val results = FilterResults()
if (prevCount == -1)
prevCount = allItems.size
if (prefix.isNullOrEmpty()) {
allItems.forEach {
if (it is MessageFull)
it.searchHighlightText = null
}
results.values = allItems.toList()
results.count = allItems.size
return results
}
val items = mutableListOf<Any>()
val prefixString = prefix.toString()
allItems.forEach {
if (it !is MessageFull) {
items.add(it)
return@forEach
}
it.filterWeight = 100
it.searchHighlightText = null
var weight: Int
if (it.type == Message.TYPE_SENT) {
it.recipients?.forEach { recipient ->
weight = getMatchWeight(recipient.fullName, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 31
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
}
else {
weight = getMatchWeight(it.senderName, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 31
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
weight = getMatchWeight(it.subject, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 22
it.filterWeight = min(it.filterWeight, 20 + weight)
}
if (it.filterWeight != 100) {
it.searchHighlightText = prefixString
items.add(it)
}
}
Collections.sort(items, comparator)
results.values = items
results.count = items.size
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
results.values?.let { items = it as MutableList<Any> }
// do not re-bind the search box
val count = results.count - 1
// this tries to update every item except the search field
when {
count > prevCount -> {
notifyItemRangeInserted(prevCount + 1, count - prevCount)
notifyItemRangeChanged(1, prevCount)
}
count < prevCount -> {
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
notifyItemRangeChanged(1, count)
}
else -> {
notifyItemRangeChanged(1, count)
}
}
/*if (prevCount != count) {
items.getOrNull(0)?.let {
if (it is MessagesSearch) {
it.count = count
notifyItemChanged(0)
}
}
}*/
prevCount = count
}
}}
private fun getMatchWeight(name: CharSequence?, prefix: String): Int {
if (name == null)
return 100
val nameClean = name.cleanDiacritics()
// First match against the whole, non-split value
if (nameClean.startsWith(prefix, ignoreCase = true) || name.startsWith(prefix, ignoreCase = true)) {
return 1
} else {
// check if prefix matches any of the words
val words = nameClean.split(" ").toTypedArray() + name.split(" ").toTypedArray()
for (word in words) {
if (word.startsWith(prefix, ignoreCase = true)) {
return 2
}
}
}
// finally check if the prefix matches any part of the name
if (nameClean.contains(prefix, ignoreCase = true) || name.contains(prefix, ignoreCase = true)) {
return 3
}
return 100
}
} }

View File

@ -12,7 +12,9 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class MessagesFragment : Fragment(), CoroutineScope { class MessagesFragment : Fragment(), CoroutineScope {
@ -100,9 +102,19 @@ class MessagesFragment : Fragment(), CoroutineScope {
fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline
} }
setFabOnClickListener(View.OnClickListener { bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
setFabOnClickListener {
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE) activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE)
}) }
} }
activity.gainAttentionFAB() activity.gainAttentionFAB()

View File

@ -33,6 +33,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: MessagesListFragmentBinding private lateinit var b: MessagesListFragmentBinding
private var adapter: MessagesAdapter? = null
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -53,21 +54,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED) val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
var topPosition = arguments.getInt("topPosition", NO_POSITION) var topPosition = arguments.getInt("topPosition", NO_POSITION)
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION) var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
val searchText = arguments.getString("searchText", "")
teachers = withContext(Dispatchers.Default) { teachers = withContext(Dispatchers.Default) {
app.db.teacherDao().getAllNow(App.profileId) app.db.teacherDao().getAllNow(App.profileId)
} }
val adapter = MessagesAdapter(activity, teachers) { adapter = MessagesAdapter(activity, teachers) {
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle( activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
"messageId" to it.id "messageId" to it.id
)) ))
} }
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { items -> app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
if (!isAdded) return@Observer if (!isAdded) return@Observer
items.forEach { message -> messages.forEach { message ->
message.recipients?.removeAll { it.profileId != message.profileId } message.recipients?.removeAll { it.profileId != message.profileId }
message.recipients?.forEach { recipient -> message.recipients?.forEach { recipient ->
if (recipient.fullName == null) { if (recipient.fullName == null) {
@ -77,13 +79,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
} }
// load & configure the adapter // load & configure the adapter
adapter.items = items.toMutableList() val items = messages.toMutableList<Any>()
adapter.items.add(0, MessagesSearch().also { items.add(0, MessagesSearch().also {
it.count = items.size it.searchText = searchText
}) })
adapter.allItems = adapter.items.toMutableList()
adapter?.items = items
adapter?.allItems = items
if (items.isNotNullNorEmpty() && b.list.adapter == null) { if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.list.adapter = adapter if (searchText.isNotBlank())
adapter?.filter?.filter(searchText) {
b.list.adapter = adapter
}
else
b.list.adapter = adapter
b.list.apply { b.list.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
@ -92,7 +103,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
addOnScrollListener(onScrollListener) addOnScrollListener(onScrollListener)
} }
} }
adapter.notifyDataSetChanged()
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty()) setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager -> (b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
@ -119,10 +130,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!isAdded) return if (!isAdded)
return
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch
onPageDestroy?.invoke(position, Bundle( onPageDestroy?.invoke(position, Bundle(
"topPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition(), "topPosition" to layoutManager?.findFirstVisibleItemPosition(),
"bottomPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition() "bottomPosition" to layoutManager?.findLastCompletelyVisibleItemPosition(),
"searchText" to searchItem?.searchText?.toString()
)) ))
} }
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.compose package pl.szczodrzynski.edziennik.ui.modules.messages.compose
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
@ -43,6 +44,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
import pl.szczodrzynski.edziennik.utils.Colors import pl.szczodrzynski.edziennik.utils.Colors
@ -50,6 +52,7 @@ import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.elevateSurface import pl.szczodrzynski.navlib.elevateSurface
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.text.replace import kotlin.text.replace
@ -67,6 +70,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private val profileConfig by lazy { app.config.forProfile().ui }
private val greetingText
get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
private var teachers = mutableListOf<Teacher>() private var teachers = mutableListOf<Teacher>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -131,6 +138,16 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
} }
}*/ }*/
activity.bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
launch { launch {
delay(100) delay(100)
getRecipientList() getRecipientList()
@ -290,7 +307,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.recipients.setIllegalCharacterIdentifier { c -> b.recipients.setIllegalCharacterIdentifier { c ->
c.toString().matches("[\\n;:_ ]".toRegex()) c.toString().matches("[\\n;:_ ]".toRegex())
} }
b.recipients.setOnChipRemoveListener { _ -> b.recipients.setOnChipRemoveListener {
b.recipients.setSelection(b.recipients.text.length) b.recipients.setSelection(b.recipients.text.length)
} }
@ -318,14 +335,15 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
fabExtendedText = getString(R.string.messages_compose_send) fabExtendedText = getString(R.string.messages_compose_send)
fabIcon = CommunityMaterial.Icon3.cmd_send_outline fabIcon = CommunityMaterial.Icon3.cmd_send_outline
setFabOnClickListener(View.OnClickListener { setFabOnClickListener {
sendMessage() sendMessage()
}) }
} }
activity.gainAttentionFAB() activity.gainAttentionFAB()
} }
@SuppressLint("SetTextI18n")
private fun updateRecipientList(list: List<Teacher>) { launch { private fun updateRecipientList(list: List<Teacher>) { launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
teachers = list.sortedBy { it.fullName }.toMutableList() teachers = list.sortedBy { it.fullName }.toMutableList()
@ -344,11 +362,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
val adapter = MessagesComposeSuggestionAdapter(activity, teachers) val adapter = MessagesComposeSuggestionAdapter(activity, teachers)
b.recipients.setAdapter(adapter) b.recipients.setAdapter(adapter)
if (profileConfig.messagesGreetingOnCompose)
b.text.setText(greetingText)
handleReplyMessage() handleReplyMessage()
handleMailToIntent() handleMailToIntent()
}} }}
private fun handleReplyMessage() { launch { private fun handleReplyMessage() = launch {
val replyMessage = arguments?.getString("message") val replyMessage = arguments?.getString("message")
if (replyMessage != null) { if (replyMessage != null) {
val chipList = mutableListOf<ChipInfo>() val chipList = mutableListOf<ChipInfo>()
@ -370,8 +391,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
if (arguments?.getString("type") == "reply") { if (arguments?.getString("type") == "reply") {
// add greeting text // add greeting text
span.replace(0, 0, "\n\nZ poważaniem,\n${app.profile.accountName if (profileConfig.messagesGreetingOnReply)
?: app.profile.studentNameLong ?: ""}\n\n\n") span.replace(0, 0, "$greetingText\n\n\n")
else
span.replace(0, 0, "\n\n")
teachers.firstOrNull { it.id == msg.senderId }?.let { teacher -> teachers.firstOrNull { it.id == msg.senderId }?.let { teacher ->
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName) teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
@ -379,7 +402,12 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
} }
subject = "Re: ${msg.subject}" subject = "Re: ${msg.subject}"
} else { } else {
span.replace(0, 0, "\n\n") // add greeting text
if (profileConfig.messagesGreetingOnForward)
span.replace(0, 0, "$greetingText\n\n\n")
else
span.replace(0, 0, "\n\n")
subject = "Fwd: ${msg.subject}" subject = "Fwd: ${msg.subject}"
} }
body = MessagesUtils.htmlToSpannable(activity, msg.body body = MessagesUtils.htmlToSpannable(activity, msg.body
@ -401,7 +429,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
else { else {
b.recipients.requestFocus() b.recipients.requestFocus()
} }
}} }
private fun handleMailToIntent() { private fun handleMailToIntent() {
val teacherId = arguments?.getLong("messageRecipientId") val teacherId = arguments?.getLong("messageRecipientId")
@ -475,10 +503,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
text.toString() text.toString()
} }
textHtml = textHtml
.replace("</b><b>", "")
.replace("</i><i>", "")
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "p")
if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) { if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) {
textHtml = textHtml textHtml = textHtml
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "span") .replace("</p><br>", "</p>")
.replace("</p>", "</span>")
.replace("<b>", "<strong>") .replace("<b>", "<strong>")
.replace("</b>", "</strong>") .replace("</b>", "</strong>")
.replace("<i>", "<em>") .replace("<i>", "<em>")

View File

@ -5,7 +5,5 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.models package pl.szczodrzynski.edziennik.ui.modules.messages.models
class MessagesSearch { class MessagesSearch {
var isFocused = false var searchText: CharSequence = ""
var searchText = ""
var count = 0
} }

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