diff --git a/.bettercodehub.yml b/.bettercodehub.yml index 349f7675..d7be5169 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,3 +1,3 @@ -component_depth: 8 +component_depth: 10 languages: - kotlin diff --git a/.circleci/config.yml b/.circleci/config.yml index ce2922ba..2cb2e147 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,7 +162,7 @@ jobs: openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks - run: name: Publish release - command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex + command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex workflows: version: 2 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7c537735 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=Advanced +indent_style=space +indent_size=4 + +[*.json] +indent_size=2 + +[*.{kt,kts}] +disabled_rules=import-ordering,no-wildcard-imports diff --git a/.gitea/FUNDING.yml b/.gitea/FUNDING.yml new file mode 100644 index 00000000..cdce0759 --- /dev/null +++ b/.gitea/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: wulkanowy +custom: https://www.paypal.com/paypalme/wulkanowy diff --git a/.github/ISSUE_TEMPLATE.md b/.gitea/ISSUE_TEMPLATE/bug_report.md similarity index 61% rename from .github/ISSUE_TEMPLATE.md rename to .gitea/ISSUE_TEMPLATE/bug_report.md index 27d57f59..237721cb 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,12 @@ +--- +name: Bug report +about: Utwórz raport błędu, aby pomóc nam ulepszyć Wulkanowego +title: '' +labels: '' +assignees: '' + +--- + ## Co powinno się dziać diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..6194a41e --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Zaproponuj nowy pomysł dla Wulkanowego +title: '' +labels: '' +assignees: '' + +--- + +** Czy Twoja prośba o funkcję jest związana z problemem? Proszę opisz.** +Jasny i zwięzły opis problemu. Np. Zawsze jestem sfrustrowany, gdy [...] + +** Opisz żądane rozwiązanie ** +Jasny i zwięzły opis tego, co chcesz, aby się wydarzyło. + +** Opisz alternatywy, które rozważałeś ** +Jasny i zwięzły opis wszelkich rozważanych alternatywnych rozwiązań lub funkcji. + +** Dodatkowy kontekst ** +Dodaj inny kontekst lub zrzuty ekranu dotyczące żądania funkcji tutaj. diff --git a/.gitea/dependabot.yml b/.gitea/dependabot.yml new file mode 100644 index 00000000..5cb45fc8 --- /dev/null +++ b/.gitea/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: gradle + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + target-branch: develop + ignore: + - dependency-name: io.github.wulkanowy:sdk + reviewers: + - Faierbel \ No newline at end of file diff --git a/.gitea/release.yml b/.gitea/release.yml new file mode 100644 index 00000000..8557fcea --- /dev/null +++ b/.gitea/release.yml @@ -0,0 +1,18 @@ +changelog: + exclude: + labels: + - "release ignore" + categories: + - title: breaking changes + labels: + - major + - title: new features + labels: + - minor + - fr:approved + - title: translation updates + labels: + - translation + - title: features + labels: + - "*" diff --git a/.gitea/workflows/build_android.yml b/.gitea/workflows/build_android.yml new file mode 100644 index 00000000..f9d83d38 --- /dev/null +++ b/.gitea/workflows/build_android.yml @@ -0,0 +1,85 @@ +name: Generate APK + +env: + main_project_module: app + +on: + pull_request: + types: + - closed + +jobs: + build: + + runs-on: ubuntu-latest + + env: + RUNNER_TOOL_CACHE: /toolcache + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + + - name: Set current date as env variable + run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + + - name: Set repository name as env variable + run: echo "repository_name=$(echo '${{ gitea.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' # See 'Supported distributions' for available options + java-version: '17' + cache: 'gradle' + + - name: Set up Go environment + uses: actions/setup-go@v3 + with: + go-version: '1.22' + + - name: Get hash of Gradle files + uses: https://gitea.com/actions/go-hashfiles@v0.0.1 + id: get-hash + with: + patterns: |- + **/*.gradle* + + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ steps.get-hash.outputs.hash }} + + - name: Get app version + id: get_version + run: echo "VERSION_NAME=$(grep -m1 "versionName" app/build.gradle | awk '{print $2}' | tr -d \''"\')" >> $GITHUB_ENV + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Build debug APK + run: ./gradlew assembleDebug + + - name: Upload artifacts + uses: actions/upload-artifact@v3 # not v4 because of GHES + with: + name: wulkanowy_mod_debug_builds + path: | + app/build/outputs/**/*-debug.apk + + - name: Create release + uses: akkuman/gitea-release-action@v1 + env: + NODE_OPTIONS: '--experimental-fetch' + with: + files: | + app/build/outputs/**/*-debug.apk + + name: Release ${{ env.VERSION_NAME }} (${{ env.date_today }}) + tag_name: v${{ env.VERSION_NAME }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d3fb6e4e..ad83ced8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ out/ # Gradle files .gradle/ build/ +.build-cache # Local configuration file (sdk path, etc) local.properties @@ -64,6 +65,12 @@ captures/ .idea/uiDesigner.xml .idea/runConfigurations.xml .idea/discord.xml +.idea/migrations.xml +.idea/androidTestResultsUserPreferences.xml +.idea/copilot +.idea/deploymentTargetDropDown.xml +.idea/deploymentTargetSelector.xml +.idea/kotlinc.xml # Keystore files *.jks @@ -110,6 +117,13 @@ Thumbs.db *.ear ### AndroidStudio Patch ### - !/gradle/wrapper/gradle-wrapper.jar .idea/jarRepositories.xml + +### Services config files +agconnect-services.json +agconnect-credentials.json +google-services.json +!app/google-services.json + + diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index d66c3361..1f93faef 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -2,25 +2,8 @@ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0f6284a4..e0b0be97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ jdk: oraclejdk8 env: global: - - ANDROID_API_LEVEL=29 - - ANDROID_BUILD_TOOLS_VERSION=29.0.3 + - ANDROID_API_LEVEL=30 + - ANDROID_BUILD_TOOLS_VERSION=30.0.2 cache: directories: @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.17.2 + - 0.24.0 android: licenses: @@ -28,40 +28,40 @@ android: - build-tools-$ANDROID_BUILD_TOOLS_VERSION # The SDK version used to compile your project - android-$ANDROID_API_LEVEL - # Additional components + # Additional components - extra-google-google_play_services - extra-google-m2repository - extra-android-m2repository - addon-google_apis-google-$ANDROID_API_LEVEL - # Android emulator + # Android emulator - android-22 - sys-img-armeabi-v7a-android-22 +before_install: + - yes | sdkmanager "platforms;android-30" + - yes | sdkmanager "build-tools;30.0.2" + before_script: - # Launch emulator before the execution - - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a - - emulator -avd test -no-audio -no-window & - - android-wait-for-emulator - - adb shell input keyevent 82 & - - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" + # Launch emulator before the execution + - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator + - adb shell input keyevent 82 & + - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" script: - ./gradlew dependencies --stacktrace --daemon - fossa --no-ansi || true - #- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon - - ./gradlew -Pcoverage testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon - - ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon + - ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace --daemon + - ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace --daemon - ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon - - if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else - git fetch --unshallow; - ./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesPlayRelease -x fabricGenerateResourcesFdroidRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon; - fi - | if [ $TRAVIS_TAG ]; then gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg; + gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; - ./gradlew publishPlayRelease -PenableCrashlytics --stacktrace; + ./gradlew publishPlayRelease --stacktrace; fi after_success: diff --git a/LICENSE b/LICENSE index 5dd9cacf..a1fc3705 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2019 Wulkanowy + Copyright 2023 Wulkanowy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.cs.md b/README.cs.md new file mode 100644 index 00000000..2d3d2a77 --- /dev/null +++ b/README.cs.md @@ -0,0 +1,33 @@ +Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) + +# Wulkanowy MOD + +## Funkce: +* skrýt známky +* Skrýt jednotlivé záznamy o docházce. +* Skrýt komentáře. +* falešná docházka % + +Chcete-li se dostat na skrytý panel: +1. Přejděte na kartu „Další“. +2. Přejděte na panel „Nastavení“. +3. Přejděte na panel „O aplikaci“. +4. Klikněte 5x na logo aplikace +5. Přejděte na domovskou obrazovku +6. Přejděte do nastavení +7. Zadejte „tajná nastavení“ + +# Instalace + +| Název souboru | Přizpůsobeno | +| ---------------- | ----------------- | +| `*-fdroid-*.apk` | F-Droid | +| `*-hms-*.apk` | Huawei AppGallery | +| `*-play-*.apk` | Play Store | + +Stáhněte si vybranou verzi z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases). +Doporučujeme stáhnout nejnovější dostupnou verzi. + +# O projektu Wulkanowy + +Chcete si přečíst více o projektu Wulkanowy? [Klikněte sem](https://github.com/wulkanowy/wulkanowy) \ No newline at end of file diff --git a/README.de.md b/README.de.md new file mode 100644 index 00000000..da16a997 --- /dev/null +++ b/README.de.md @@ -0,0 +1,33 @@ +[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) + +# Wulkanowy MOD + +## Funktionen: +* Noten ausblenden +* Individuelle Anwesenheitslisten ausblenden. +* Kommentare ausblenden. +* Anwesenheit fälschen % + +So gelangen Sie zum ausgeblendeten Bereich: +1. Gehen Sie zur Registerkarte „Mehr“. +2. Gehen Sie zum Bereich „Einstellungen“. +3. Gehen Sie zum Bereich „Über die Anwendung“. +4. Klicken Sie fünfmal auf das Anwendungslogo +5. Gehen Sie zum Startbildschirm +6. Gehen Sie zu den Einstellungen +7. Geben Sie „Geheime Einstellungen“ ein + +# Installation + +| Dateiname | Angepasst an | +| ---------------- | ----------------- | +| `*-fdroid-*.apk` | F-Droid | +| `*-hms-*.apk` | Huawei AppGallery | +| `*-play-*.apk` | Play Store | + +Laden Sie die ausgewählte Version von [hier](https://git.sador.me/sadorowo/wulkanowy-mod/releases) herunter. +Wir empfehlen, die neueste verfügbare Version herunterzuladen. + +# Über das Wulkanowy-Projekt + +Möchten Sie mehr über das Wulkanowy-Projekt lesen? [Hier klicken](https://github.com/wulkanowy/wulkanowy) \ No newline at end of file diff --git a/README.en.md b/README.en.md index 7444ccca..9365c9b4 100644 --- a/README.en.md +++ b/README.en.md @@ -1,61 +1,33 @@ -[Polska wersja README](README.md) +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) -# Wulkanowy -[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) -[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) -[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) -[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) -[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +# Wulkanowy MOD -Unofficial android VULCAN UONET+ register client for both students and their parents +## Functions: +* hide grades +* hide individual attendance entries +* hide comments +* fake attendance %. -## Features +To get to the hidden panel: +1. Go to the "More" tab +2. Go to the "Settings" panel +3. Go to the "About application" panel +4. Click on the application logo 5 times +5. Go to the home screen +6. Go to settings +7. Enter "secret settings" + +# Installation -* logging in using the email and password OR using token and pin -* functions from the register website: - * grades - * grade statistics - * attendance - * percentage of attendance - * exams - * timetable - * completed lessons - * messages - * homework - * notes - * lucky number -* calculation of the average independently of school's preferences -* notifications, e.g. about a new grade -* dark and black (AMOLED) theme -* offline mode -* no ads +| File name | Adapted to | +| ---------------- | ----------------- | +| `*-fdroid-*.apk` | F-Droid | +| `*-hms-*.apk` | Huawei AppGallery | +| `*-play-*.apk` | Play Store | -## Download +Download application from [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases). +We recommend downloading the latest available version. -You can download the current beta version from the Google Play or the F-Droid store +# About the Wulkanowy project -[Get it on Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy) -[Get it on F-Droid](https://f-droid.org/packages/io.github.wulkanowy/) - -You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release - -## Built With - - -* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) -* [RxJava 2](https://github.com/ReactiveX/RxJava) -* [Dagger 2](https://github.com/google/dagger) -* [Room](https://developer.android.com/topic/libraries/architecture/room) -* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) - -## Contributing - -Please contribute to the project either by creating a PR or submitting an issue on GitHub. - -## License - -This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details +Want to read more about the Wulkanowy project? [Click here](https://github.com/wulkanowy/wulkanowy) \ No newline at end of file diff --git a/README.md b/README.md index 61b13444..7a265ff1 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,33 @@ -[English version of README](README.en.md) +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md) -# Wulkanowy -[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) -[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) -[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) -[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) -[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +# Wulkanowy MOD -Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica +## Funkcje: +* ukryj oceny +* ukryj poszczególne wpisy frekwencji +* ukryj uwagi +* sfałszuj % frekwencji -## Funkcje +Aby dostać się do ukrytego panelu: +1. Przejdź do karty "Więcej" +2. Przejdź do panelu "Ustawienia" +3. Przejdź do panelu "O aplikacji" +4. Kliknij 5 razy w logo aplikacji +5. Przejdź na ekran główny +6. Wejdź w ustawienia +7. Wejdź w "sekretne ustawienia" + +# Instalacja -* logowanie za pomocą e-maila i hasła LUB tokena i pinu -* funkcje ze strony internetowej dziennika: - * oceny - * statystyki ocen - * frekwencja - * procent frekwencji - * sprawdziany - * plan lekcji - * lekcje zrealizowane - * wiadomości - * zadania domowe - * uwagi - * szczęśliwy numerek -* obliczanie średniej niezależnie od preferencji szkoły -* powiadomienia np. o nowej ocenie -* ciemny i czarny (AMOLED) motyw -* tryb offilne -* brak reklam +| Nazwa pliku | Przystosowana do | +| ---------------- | ----------------- | +| `*-fdroid-*.apk` | F-Droid | +| `*-hms-*.apk` | Huawei AppGallery | +| `*-play-*.apk` | Sklep Play | -## Pobierz +Pobierz wybraną wersję z [wydań](https://git.sador.me/sadorowo/wulkanowy-mod/releases). +Zalecamy pobranie najnowszej dostępnej wersji. -Aktualną wersję beta możesz pobrać ze sklepu Google Play lub F-Droid +# O projekcie Wulkanowy -[Pobierz z Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy) -[Pobierz z F-Droid](https://f-droid.org/packages/io.github.wulkanowy/) - - -Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania - - -## Zbudowana za pomocą - -* [Wulkanowy SDK](https://github.com/wulkanowy/SDK) -* [RxJava 2](https://github.com/ReactiveX/RxJava) -* [Dagger 2](https://github.com/google/dagger) -* [Room](https://developer.android.com/topic/libraries/architecture/room) -* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) - -## Współpraca - -Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub. - -## Licencja - -Ten projekt udostępniany jest na licencji Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE) +Chcesz poczytać więcej o projekcie Wulkanowy? [Kliknij tutaj](https://github.com/wulkanowy/wulkanowy) \ No newline at end of file diff --git a/README.sk.md b/README.sk.md new file mode 100644 index 00000000..570ac6d6 --- /dev/null +++ b/README.sk.md @@ -0,0 +1,33 @@ +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia + +# Wulkanowy MOD + +## Funkcie: +* skryť známky +* Skryť individuálne záznamy o dochádzke. +* Skryť komentáre. +* falošná dochádzka % + +Ak chcete prejsť na skrytý panel: +1. Prejdite na kartu „Viac“. +2. Prejdite na panel „Nastavenia“. +3. Prejdite na panel „O aplikácii“. +4. Kliknite 5-krát na logo aplikácie +5. Prejdite na domovskú obrazovku +6. Prejdite do nastavení +7. Zadajte „tajné nastavenia“ + +# Inštalácia + +| Názov súboru | Prispôsobené | +| ---------------- | ----------------- | +| `*-fdroid-*.apk` | F-Droid | +| `*-hms-*.apk` | Huawei AppGallery | +| `*-play-*.apk` | Play Store | + +Stiahnite si vybranú verziu z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases). +Odporúčame stiahnuť najnovšiu dostupnú verziu. + +# O projekte Wulkanowy + +Chcete si prečítať viac o projekte Wulkanowy? [Kliknite sem](https://github.com/wulkanowy/wulkanowy) \ No newline at end of file diff --git a/app/bitrise.jks.gpg b/app/bitrise.jks.gpg new file mode 100644 index 00000000..1dee91bd Binary files /dev/null and b/app/bitrise.jks.gpg differ diff --git a/app/build.gradle b/app/build.gradle index 2480ff47..1fb8f09c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,44 +1,52 @@ +import com.github.triplet.gradle.androidpublisher.ReleaseStatus +import ru.cian.huawei.publish.ReleaseNote + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'io.fabric' +apply plugin: 'kotlinx-serialization' +apply plugin: 'kotlin-parcelize' +apply plugin: 'com.google.devtools.ksp' +apply plugin: 'dagger.hilt.android.plugin' +apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.github.triplet.play' +apply plugin: 'ru.cian.huawei-publish' apply plugin: 'com.mikepenz.aboutlibraries.plugin' +apply plugin: 'com.huawei.agconnect' +apply plugin: 'kotlin-kapt' apply from: 'jacoco.gradle' apply from: 'sonarqube.gradle' apply from: 'hooks.gradle' android { - compileSdkVersion 29 - buildToolsVersion '29.0.3' + namespace 'io.github.wulkanowy' + compileSdk 34 defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" - minSdkVersion 17 - targetSdkVersion 29 - versionCode 56 - versionName "0.17.2" - multiDexEnabled true + minSdkVersion 21 + targetSdkVersion 34 + versionCode 167 + versionName "2.6.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true - manifestPlaceholders = [ - fabric_api_key : System.getenv("FABRIC_API_KEY") ?: "null", - crashlytics_enabled: project.hasProperty("enableCrashlytics") - ] - javaCompileOptions { - annotationProcessorOptions { - arguments = [ - "room.schemaLocation": "$projectDir/schemas".toString(), - "room.incremental" : "true" - ] - } + + resValue "string", "app_name", "Wulkanowy" + manifestPlaceholders = [admob_project_id: ""] + + buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" + buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null" + + if (System.env.SET_BUILD_TIMESTAMP) { + buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis()) + } else { + buildConfigField "long", "BUILD_TIMESTAMP", "1486235849000" } } sourceSets { - androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + // https://github.com/robolectric/robolectric/issues/3928#issuecomment-395309991 + debug.assets.srcDirs += files("$projectDir/schemas".toString()) } signingConfigs { @@ -52,50 +60,88 @@ android { buildTypes { release { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true" minifyEnabled true shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release + // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + // signingConfig signingConfigs.release + buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" + buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' } debug { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", project.hasProperty("enableCrashlytics") ? "true" : "false" + minifyEnabled false + shrinkResources false + resValue "string", "app_name", "Wulkanowy DEV" applicationIdSuffix ".dev" versionNameSuffix "-dev" - testCoverageEnabled = project.hasProperty('coverage') - ext.enableCrashlytics = project.hasProperty("enableCrashlytics") + buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" + buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' } } - flavorDimensions "platform" + flavorDimensions += "platform" productFlavors { + hms { + dimension "platform" + manifestPlaceholders = [install_channel: "AppGallery"] + } + play { dimension "platform" + manifestPlaceholders = [ + install_channel : "Google Play", + admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713" + ] + buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\"" + buildConfigField "String", "DASHBOARD_TILE_AD_ID", "\"${System.getenv("DASHBOARD_TILE_AD_ID") ?: "ca-app-pub-3940256099942544/6300978111"}\"" + } fdroid { - buildConfigField "boolean", "CRASHLYTICS_ENABLED", "false" dimension "platform" + manifestPlaceholders = [install_channel: "F-Droid"] } } - lintOptions { - disable 'HardwareIds' + playConfigs { + play { enabled.set(true) } + } + + buildFeatures { + viewBinding true + buildConfig true + } + + bundle { + language { + enableSplit = false + } + } + + testOptions { + unitTests.includeAndroidResources = true + // workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750 + unitTests.all { jvmArgs '-noverify' } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" + freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] } packagingOptions { - exclude 'META-INF/library_release.kotlin_module' - exclude 'META-INF/library-core_release.kotlin_module' + resources { + excludes += ['META-INF/library_release.kotlin_module', + 'META-INF/library-core_release.kotlin_module', + 'META-INF/LICENSE.md', + 'META-INF/LICENSE-notice.md'] + } } aboutLibraries { @@ -103,113 +149,142 @@ android { } } -androidExtensions { - experimental = true +kapt { + correctErrorTypes true +} +ksp { + arg("room.schemaLocation", "$projectDir/schemas".toString()) } play { - serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" - serviceAccountCredentials = file('key.p12') defaultToAppBundles = false - track = 'alpha' + track = 'production' + releaseStatus = ReleaseStatus.IN_PROGRESS + userFraction = 0.99d + updatePriority = 2 + enabled.set(false) +} + +huaweiPublish { + instances { + hmsRelease { + credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json" + buildFormat = "aab" + deployType = "publish" + releaseNotes = [ + new ReleaseNote( + "pl-PL", + "$projectDir/src/main/play/release-notes/pl-PL/default.txt" + ) + ] + } + } } ext { - work_manager = "2.3.4" - room = "2.2.5" - dagger = "2.27" - // don't update https://github.com/ChuckerTeam/chucker/issues/242 - chucker = "3.2.0" - mockk = "1.9.2" -} - -configurations.all { - resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3" + work_manager = "2.9.0" + android_hilt = "1.2.0" + room = "2.6.1" + chucker = "4.0.0" + mockk = "1.13.10" + coroutines = "1.8.0" } dependencies { - implementation "io.github.wulkanowy:sdk:0.17.2" + implementation 'io.github.wulkanowy:sdk:2.6.6' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.2.0" - implementation "androidx.activity:activity-ktx:1.1.0" - implementation "androidx.appcompat:appcompat:1.2.0-beta01" - implementation "androidx.appcompat:appcompat-resources:1.1.0" - implementation "androidx.fragment:fragment-ktx:1.2.4" - implementation "androidx.annotation:annotation:1.1.0" - implementation "androidx.multidex:multidex:2.0.1" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' - implementation "androidx.preference:preference-ktx:1.1.1" - implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "androidx.viewpager:viewpager:1.0.0" - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01" - implementation "androidx.constraintlayout:constraintlayout:1.1.3" - implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" - implementation "com.google.android.material:material:1.1.0" - implementation "com.github.wulkanowy:material-chips-input:2.0.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" + + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.core:core-splashscreen:1.0.1' + implementation "androidx.activity:activity-ktx:1.9.0" + implementation "androidx.appcompat:appcompat:1.6.1" + implementation "androidx.fragment:fragment-ktx:1.7.0" + implementation "androidx.annotation:annotation:1.7.1" + + implementation "androidx.preference:preference-ktx:1.2.1" + implementation "androidx.recyclerview:recyclerview:1.3.2" + implementation "androidx.viewpager2:viewpager2:1.1.0-rc01" + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + implementation "androidx.constraintlayout:constraintlayout:2.1.4" + implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" + implementation "com.google.android.material:material:1.10.0" + implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" - implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" + implementation 'com.github.lopspower:CircularImageView:4.3.0' - implementation "androidx.work:work-runtime-ktx:$work_manager" - implementation "androidx.work:work-rxjava2:$work_manager" - implementation "androidx.work:work-gcm:$work_manager" + implementation "androidx.work:work-runtime:$work_manager" + playImplementation "androidx.work:work-gcm:$work_manager" - implementation 'com.github.PaulinaSadowska:RxWorkManagerObservers:1.0.0' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0" implementation "androidx.room:room-runtime:$room" - implementation "androidx.room:room-rxjava2:$room" implementation "androidx.room:room-ktx:$room" - kapt "androidx.room:room-compiler:$room" + ksp "androidx.room:room-compiler:$room" - implementation "com.google.dagger:dagger-android-support:$dagger" - kapt "com.google.dagger:dagger-compiler:$dagger" - kapt "com.google.dagger:dagger-android-processor:$dagger" - implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2" - kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2" + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + kapt "androidx.hilt:hilt-compiler:$android_hilt" + implementation "androidx.hilt:hilt-work:$android_hilt" - implementation "eu.davidea:flexible-adapter:5.1.0" - implementation "eu.davidea:flexible-adapter-ui:1.0.0" - implementation "com.aurelhubert:ahbottomnavigation:2.3.4" - implementation "com.ncapdevi:frag-nav:3.3.0" - implementation "com.github.YarikSOffice:lingver:1.2.1" + implementation 'com.github.ncapdevi:FragNav:3.3.0' + implementation "com.github.YarikSOffice:lingver:1.3.0" - implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6" - implementation "io.reactivex.rxjava2:rxandroid:2.1.1" - implementation "io.reactivex.rxjava2:rxjava:2.2.19" + implementation 'com.squareup.retrofit2:retrofit:2.11.0' + implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" + implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" + implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" - implementation "com.google.code.gson:gson:2.8.6" - implementation "com.jakewharton.threetenabp:threetenabp:1.2.3" - implementation "com.jakewharton.timber:timber:4.7.1" - implementation "at.favre.lib:slf4j-timber:1.0.1" - implementation "fr.bipi.treessence:treessence:0.3.2" + implementation "com.jakewharton.timber:timber:5.0.1" + implementation 'com.github.Faierbel:slf4j-timber:2.0' + implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" - implementation 'com.wdullaer:materialdatetimepicker:4.2.3' - implementation "io.coil-kt:coil:0.9.5" + implementation 'io.coil-kt:coil:2.6.0' + implementation "io.github.wulkanowy:AppKillerManager:3.0.1" + implementation 'me.xdrop:fuzzywuzzy:1.4.0' + implementation 'com.fredporciuncula:flow-preferences:1.9.1' + implementation 'org.apache.commons:commons-text:1.12.0' - playImplementation 'com.google.firebase:firebase-analytics:17.3.0' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.5' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.5" - playImplementation "com.google.firebase:firebase-messaging:20.1.0" - playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" - playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' + playImplementation platform('com.google.firebase:firebase-bom:33.0.0') + playImplementation 'com.google.firebase:firebase-analytics' + playImplementation 'com.google.firebase:firebase-messaging' + playImplementation 'com.google.firebase:firebase-crashlytics:' + playImplementation 'com.google.firebase:firebase-config' - releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" + playImplementation 'com.google.android.gms:play-services-ads:22.6.0' + playImplementation "com.google.android.play:integrity:1.3.0" + playImplementation 'com.google.android.play:app-update-ktx:2.1.0' + playImplementation 'com.google.android.play:review-ktx:2.0.1' + playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" - debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" - debugImplementation "com.amitshekhar.android:debug-db:1.0.6" + hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' - testImplementation "junit:junit:4.13" + releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" + + debugImplementation "com.github.chuckerteam.chucker:library:$chucker" + debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' + debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04' + + testImplementation "junit:junit:4.13.2" testImplementation "io.mockk:mockk:$mockk" - testImplementation "org.threeten:threetenbp:1.4.3" - testImplementation "org.mockito:mockito-inline:3.3.3" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - androidTestImplementation "androidx.test:core:1.2.0" - androidTestImplementation "androidx.test:runner:1.2.0" - androidTestImplementation "androidx.test.ext:junit:1.1.1" + testImplementation 'org.robolectric:robolectric:4.12.1' + testImplementation "androidx.test:runner:1.5.2" + testImplementation "androidx.test.ext:junit:1.1.5" + testImplementation "androidx.test:core:1.5.0" + testImplementation "androidx.room:room-testing:$room" + testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" + + androidTestImplementation "androidx.test:core:1.5.0" + androidTestImplementation "androidx.test:runner:1.5.2" + androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "io.mockk:mockk-android:$mockk" - androidTestImplementation "androidx.room:room-testing:$room" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - androidTestImplementation "org.mockito:mockito-android:3.3.3" } - -apply plugin: 'com.google.gms.google-services' diff --git a/app/src/debug/google-services.json b/app/google-services.json similarity index 56% rename from app/src/debug/google-services.json rename to app/google-services.json index e9303986..2f71b854 100644 --- a/app/src/debug/google-services.json +++ b/app/google-services.json @@ -36,6 +36,37 @@ "status": 2 } } + }, + { + "client_info": { + "mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1", + "android_client_info": { + "package_name": "io.github.wulkanowy" + } + }, + "oauth_client": [ + { + "client_id": "", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } } ], "configuration_version": "1" diff --git a/app/jacoco.gradle b/app/jacoco.gradle index e9abfb61..67ffdb13 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -1,22 +1,23 @@ apply plugin: "jacoco" jacoco { - toolVersion "0.8.5" - reportsDir = file("$buildDir/reports") + toolVersion "0.8.11" + reportsDirectory.set(file("$buildDir/reports")) } -tasks.withType(Test) { +tasks.withType(Test).configureEach { jacoco.includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] } -task jacocoTestReport(type: JacocoReport) { +tasks.register('jacocoTestReport', JacocoReport) { group = "Reporting" description = "Generate Jacoco coverage reports" reports { - xml.enabled = true - html.enabled = true + xml.required.set(true) + html.required.set(true) } def excludes = ['**/R.class', @@ -32,19 +33,19 @@ task jacocoTestReport(type: JacocoReport) { '**/*_Factory.*'] classDirectories.setFrom(fileTree( - dir: "$buildDir/intermediates/classes/debug", - excludes: excludes + dir: "$buildDir/intermediates/classes/debug", + excludes: excludes ) + fileTree( - dir: "$buildDir/tmp/kotlin-classes/playDebug", - excludes: excludes + dir: "$buildDir/tmp/kotlin-classes/fdroidDebug", + excludes: excludes )) sourceDirectories.setFrom(files([ - "src/main/java", - "src/play/java" + "src/main/java", + "src/fdroid/java" ])) executionData.setFrom(fileTree( - dir: project.projectDir, - includes: ["**/*.exec", "**/*.ec"] + dir: project.projectDir, + includes: ["**/*.exec", "**/*.ec"] )) } diff --git a/app/key.p12.gpg b/app/key.p12.gpg deleted file mode 100644 index e9b6d06e..00000000 Binary files a/app/key.p12.gpg and /dev/null differ diff --git a/app/play-publish-lint.sh b/app/play-publish-lint.sh index d3354b1a..5f0391de 100755 --- a/app/play-publish-lint.sh +++ b/app/play-publish-lint.sh @@ -1,7 +1,8 @@ #!/bin/bash - content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit -if [[ "${#content}" -gt 500 ]]; then +content2=echo "$content" | dos2unix +if [[ "${#content2}" -gt 500 ]]; then echo >&2 "Release notes content has reached the limit of 500 characters" exit 1 fi diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1a8b8c32..0fd49f6a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,40 +1,22 @@ -# Optimizations --optimizationpasses 5 --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontskipnonpubliclibraryclassmembers --dontpreverify +# General -dontobfuscate --allowaccessmodification --repackageclasses '' --verbose +-ignorewarnings -#Keep all wulkanowy files +#Config for wulkanowy -keep class io.github.wulkanowy.** {*;} -#Config for anallitycs --keepattributes *Annotation* +#Config for firebase crashlitycs -keepattributes SourceFile,LineNumberTable --keep class com.crashlytics.** {*;} -keep public class * extends java.lang.Exception --dontwarn com.crashlytics.** -#Config for OkHttp +#Config for Okio and OkHttp +-dontwarn javax.annotation.** -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase -dontwarn org.codehaus.mojo.animal_sniffer.* -dontwarn okhttp3.internal.platform.ConscryptPlatform --dontwarn javax.annotation.** - - -#Config for ReactiveNetwork --dontwarn com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork --dontwarn io.reactivex.functions.Function --dontwarn rx.internal.util.** --dontwarn sun.misc.Unsafe #Config for MPAndroidChart @@ -45,8 +27,16 @@ -keep class com.google.android.material.tabs.** { *; } -#Config for About Libraries --keep class .R --keep class **.R$* { - ; -} +#Config for HMS SDK +-keepattributes *Annotation* +-keepattributes Exceptions +-keepattributes InnerClasses +-keepattributes Signature +-keep class com.huawei.agconnect.**{*;} +-keep class com.huawei.hianalytics.**{*;} +-keep class com.huawei.updatesdk.**{*;} +-keep class com.huawei.hms.**{*;} + + +#Config for Wulkanowy SDK +-keep,allowobfuscation,allowshrinking class retrofit2.Response diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json index e99a11d4..474824df 100644 --- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json @@ -1741,4 +1741,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')" ] } -} \ No newline at end of file +} diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json new file mode 100644 index 00000000..21005f9c --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json @@ -0,0 +1,1768 @@ +{ + "formatVersion": 1, + "database": { + "version": 26, + "identityHash": "43f8777ffe95a5a2850ee981db3dbe2e", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '43f8777ffe95a5a2850ee981db3dbe2e')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/27.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/27.json new file mode 100644 index 00000000..46b77751 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/27.json @@ -0,0 +1,1774 @@ +{ + "formatVersion": 1, + "database": { + "version": 27, + "identityHash": "b24d2d9662e26dd31f2eea78a297558c", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b24d2d9662e26dd31f2eea78a297558c')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json new file mode 100644 index 00000000..c7c4c033 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json @@ -0,0 +1,1842 @@ +{ + "formatVersion": 1, + "database": { + "version": 28, + "identityHash": "3a449a55ea73fbfbb7973f1f3f834e10", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3a449a55ea73fbfbb7973f1f3f834e10')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json new file mode 100644 index 00000000..3e863c57 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json @@ -0,0 +1,1898 @@ +{ + "formatVersion": 1, + "database": { + "version": 29, + "identityHash": "30e4647c7dd84a6ac9b2f9f3ef7d3264", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '30e4647c7dd84a6ac9b2f9f3ef7d3264')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json new file mode 100644 index 00000000..309f4ac0 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json @@ -0,0 +1,1954 @@ +{ + "formatVersion": 1, + "database": { + "version": 30, + "identityHash": "891980e378373d0a17bd341f9b07cc74", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '891980e378373d0a17bd341f9b07cc74')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json new file mode 100644 index 00000000..4935a901 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json @@ -0,0 +1,2136 @@ +{ + "formatVersion": 1, + "database": { + "version": 31, + "identityHash": "d642512ffa5fe81ae9308c9c55612539", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT NOT NULL, `first_guardian_kinship` TEXT NOT NULL, `first_guardian_address` TEXT NOT NULL, `first_guardian_phones` TEXT NOT NULL, `first_guardian_email` TEXT NOT NULL, `second_guardian_full_name` TEXT NOT NULL, `second_guardian_kinship` TEXT NOT NULL, `second_guardian_address` TEXT NOT NULL, `second_guardian_phones` TEXT NOT NULL, `second_guardian_email` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd642512ffa5fe81ae9308c9c55612539')" + ] + } +} diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json new file mode 100644 index 00000000..3621be48 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json @@ -0,0 +1,2142 @@ +{ + "formatVersion": 1, + "database": { + "version": 32, + "identityHash": "9531cdc8b3f0e62db5ab6ebe66837a28", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT NOT NULL, `first_guardian_kinship` TEXT NOT NULL, `first_guardian_address` TEXT NOT NULL, `first_guardian_phones` TEXT NOT NULL, `first_guardian_email` TEXT NOT NULL, `second_guardian_full_name` TEXT NOT NULL, `second_guardian_kinship` TEXT NOT NULL, `second_guardian_address` TEXT NOT NULL, `second_guardian_phones` TEXT NOT NULL, `second_guardian_email` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9531cdc8b3f0e62db5ab6ebe66837a28')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/33.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/33.json new file mode 100644 index 00000000..255c196e --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/33.json @@ -0,0 +1,2142 @@ +{ + "formatVersion": 1, + "database": { + "version": 33, + "identityHash": "c024cc4e19e009a03303e2bfe5c34b48", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c024cc4e19e009a03303e2bfe5c34b48')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json new file mode 100644 index 00000000..6a56ac64 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json @@ -0,0 +1,2142 @@ +{ + "formatVersion": 1, + "database": { + "version": 34, + "identityHash": "c024cc4e19e009a03303e2bfe5c34b48", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c024cc4e19e009a03303e2bfe5c34b48')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json new file mode 100644 index 00000000..f166b2d7 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json @@ -0,0 +1,2148 @@ +{ + "formatVersion": 1, + "database": { + "version": 35, + "identityHash": "15fb91ec180fe60bf400cfa729c3418d", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '15fb91ec180fe60bf400cfa729c3418d')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/36.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/36.json new file mode 100644 index 00000000..2fae5302 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/36.json @@ -0,0 +1,2160 @@ +{ + "formatVersion": 1, + "database": { + "version": 36, + "identityHash": "a217c2fc4e756a014f203e7499f88a90", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a217c2fc4e756a014f203e7499f88a90')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/37.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/37.json new file mode 100644 index 00000000..1aaf96f6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/37.json @@ -0,0 +1,2204 @@ +{ + "formatVersion": 1, + "database": { + "version": 37, + "identityHash": "98169e620b79a6633f80b5bd32162ed2", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '98169e620b79a6633f80b5bd32162ed2')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/38.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/38.json new file mode 100644 index 00000000..1625a9f6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/38.json @@ -0,0 +1,2248 @@ +{ + "formatVersion": 1, + "database": { + "version": 38, + "identityHash": "02c6cf89e47f5ee42ee04880eb91cff2", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02c6cf89e47f5ee42ee04880eb91cff2')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/39.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/39.json new file mode 100644 index 00000000..d93351ac --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/39.json @@ -0,0 +1,2260 @@ +{ + "formatVersion": 1, + "database": { + "version": 39, + "identityHash": "73b2b5a236cacd91df734ec4141bc590", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '73b2b5a236cacd91df734ec4141bc590')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json new file mode 100644 index 00000000..362c7f0e --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json @@ -0,0 +1,2316 @@ +{ + "formatVersion": 1, + "database": { + "version": 40, + "identityHash": "e2fba6244951713b4e9b217adc5d1a23", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2fba6244951713b4e9b217adc5d1a23')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json new file mode 100644 index 00000000..9d008060 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json @@ -0,0 +1,2322 @@ +{ + "formatVersion": 1, + "database": { + "version": 41, + "identityHash": "d9ce44a78495a358606612bd91603c0f", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9ce44a78495a358606612bd91603c0f')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json new file mode 100644 index 00000000..a5faa57b --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json @@ -0,0 +1,2396 @@ +{ + "formatVersion": 1, + "database": { + "version": 42, + "identityHash": "5c8b7f9409294ecdebf9f74a44f8e883", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5c8b7f9409294ecdebf9f74a44f8e883')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json new file mode 100644 index 00000000..22c0d812 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json @@ -0,0 +1,2408 @@ +{ + "formatVersion": 1, + "database": { + "version": 43, + "identityHash": "66946510bb620ae82686a5a1a31aba18", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '66946510bb620ae82686a5a1a31aba18')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/44.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/44.json new file mode 100644 index 00000000..4dc9834d --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/44.json @@ -0,0 +1,2414 @@ +{ + "formatVersion": 1, + "database": { + "version": 44, + "identityHash": "e3437dc0b229a325bbeb3e964a500530", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e3437dc0b229a325bbeb3e964a500530')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json new file mode 100644 index 00000000..57f3d431 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json @@ -0,0 +1,2430 @@ +{ + "formatVersion": 1, + "database": { + "version": 45, + "identityHash": "f310243440ca00cbc35e62ebaca5c7d8", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f310243440ca00cbc35e62ebaca5c7d8')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json new file mode 100644 index 00000000..04518141 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json @@ -0,0 +1,2430 @@ +{ + "formatVersion": 1, + "database": { + "version": 46, + "identityHash": "f310243440ca00cbc35e62ebaca5c7d8", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f310243440ca00cbc35e62ebaca5c7d8')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json new file mode 100644 index 00000000..3f8291ea --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json @@ -0,0 +1,2438 @@ +{ + "formatVersion": 1, + "database": { + "version": 47, + "identityHash": "ac88c80d4bb923b22f22ce4f91521306", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ac88c80d4bb923b22f22ce4f91521306')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json new file mode 100644 index 00000000..1c11aae9 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json @@ -0,0 +1,2445 @@ +{ + "formatVersion": 1, + "database": { + "version": 48, + "identityHash": "95751b933ad9f835ffc1805f4ef71bdb", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '95751b933ad9f835ffc1805f4ef71bdb')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/49.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/49.json new file mode 100644 index 00000000..5472fb78 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/49.json @@ -0,0 +1,2445 @@ +{ + "formatVersion": 1, + "database": { + "version": 49, + "identityHash": "790d4dc0e11f38349c49af85fabf9b7b", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '790d4dc0e11f38349c49af85fabf9b7b')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/50.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/50.json new file mode 100644 index 00000000..4361db95 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/50.json @@ -0,0 +1,2445 @@ +{ + "formatVersion": 1, + "database": { + "version": 50, + "identityHash": "87455aae2b15baa976386c833afa9cd9", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '87455aae2b15baa976386c833afa9cd9')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/51.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/51.json new file mode 100644 index 00000000..271b8c90 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/51.json @@ -0,0 +1,2409 @@ +{ + "formatVersion": 1, + "database": { + "version": 51, + "identityHash": "51f9cb1d80df003c03bb655c0162487c", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `userLoginId` INTEGER NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "userLoginId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '51f9cb1d80df003c03bb655c0162487c')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json new file mode 100644 index 00000000..129d1917 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json @@ -0,0 +1,2421 @@ +{ + "formatVersion": 1, + "database": { + "version": 52, + "identityHash": "8742176f26afcc81279d4a073dca2949", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `userLoginId` INTEGER NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "userLoginId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8742176f26afcc81279d4a073dca2949')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json new file mode 100644 index 00000000..98561787 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json @@ -0,0 +1,2439 @@ +{ + "formatVersion": 1, + "database": { + "version": 53, + "identityHash": "1dc96a366125ec9f8567da87cdc9c863", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dc96a366125ec9f8567da87cdc9c863')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json new file mode 100644 index 00000000..7b41672b --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json @@ -0,0 +1,2439 @@ +{ + "formatVersion": 1, + "database": { + "version": 54, + "identityHash": "1dc96a366125ec9f8567da87cdc9c863", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dc96a366125ec9f8567da87cdc9c863')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json new file mode 100644 index 00000000..60c2efbe --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json @@ -0,0 +1,2435 @@ +{ + "formatVersion": 1, + "database": { + "version": 55, + "identityHash": "cba22eea6d26cf4d6b9a388ba3329a12", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "message_global_key", + "url", + "filename" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cba22eea6d26cf4d6b9a388ba3329a12')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json new file mode 100644 index 00000000..1a26e717 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json @@ -0,0 +1,2442 @@ +{ + "formatVersion": 1, + "database": { + "version": 56, + "identityHash": "48f0538bd21601eb5322a7d850e04134", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '48f0538bd21601eb5322a7d850e04134')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json new file mode 100644 index 00000000..2eff1223 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json @@ -0,0 +1,2443 @@ +{ + "formatVersion": 1, + "database": { + "version": 57, + "identityHash": "d15dbe7d7e4d7df98ec98d9a3a4b5fcd", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd15dbe7d7e4d7df98ec98d9a3a4b5fcd')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json new file mode 100644 index 00000000..e6e71229 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json @@ -0,0 +1,2451 @@ +{ + "formatVersion": 1, + "database": { + "version": 58, + "identityHash": "cd1d4f8f2b6e3860fbc1de93d4f5ca42", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cd1d4f8f2b6e3860fbc1de93d4f5ca42')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json new file mode 100644 index 00000000..a3f2e0dc --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json @@ -0,0 +1,2501 @@ +{ + "formatVersion": 1, + "database": { + "version": 59, + "identityHash": "3bd95e40b587e8131a2a2c23aee538c1", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3bd95e40b587e8131a2a2c23aee538c1')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json new file mode 100644 index 00000000..20eacad1 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json @@ -0,0 +1,2527 @@ +{ + "formatVersion": 1, + "database": { + "version": 60, + "identityHash": "3672d3f4d5e6b874e5a22d2bb458dc65", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3672d3f4d5e6b874e5a22d2bb458dc65')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json new file mode 100644 index 00000000..e36dcc8a --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json @@ -0,0 +1,2533 @@ +{ + "formatVersion": 1, + "database": { + "version": 61, + "identityHash": "41fbd2ff00aba10b2ef0a079e6037c87", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `author` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '41fbd2ff00aba10b2ef0a079e6037c87')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json new file mode 100644 index 00000000..ab63c679 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json @@ -0,0 +1,2547 @@ +{ + "formatVersion": 1, + "database": { + "version": 62, + "identityHash": "ee2464d218b254ca868667c0fc756c0b", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER NOT NULL DEFAULT 0, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `author` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee2464d218b254ca868667c0fc756c0b')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json new file mode 100644 index 00000000..9c774bb4 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json @@ -0,0 +1,2547 @@ +{ + "formatVersion": 1, + "database": { + "version": 63, + "identityHash": "8c04a56e74b1c4f55302f28ede94cac0", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER DEFAULT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `author` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8c04a56e74b1c4f55302f28ede94cac0')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json new file mode 100644 index 00000000..178a5eab --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json @@ -0,0 +1,2559 @@ +{ + "formatVersion": 1, + "database": { + "version": 64, + "identityHash": "dd5446e82ad8d0a65c545a5dbbaeb81c", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER DEFAULT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `points_sum_all_year` TEXT, `average` REAL NOT NULL, `average_all_year` REAL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSumAllYear", + "columnName": "points_sum_all_year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "averageAllYear", + "columnName": "average_all_year", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `author` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dd5446e82ad8d0a65c545a5dbbaeb81c')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt deleted file mode 100644 index 611161e5..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.db.migrations - -import androidx.preference.PreferenceManager -import androidx.room.Room -import androidx.room.testing.MigrationTestHelper -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import androidx.test.core.app.ApplicationProvider -import androidx.test.platform.app.InstrumentationRegistry -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.SharedPrefProvider -import org.junit.Rule - -abstract class AbstractMigrationTest { - - val dbName = "migration-test" - - @get:Rule - val helper: MigrationTestHelper = MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - AppDatabase::class.java.canonicalName, - FrameworkSQLiteOpenHelperFactory() - ) - - fun getMigratedRoomDatabase(): AppDatabase { - val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(), - AppDatabase::class.java, dbName) - .addMigrations(*AppDatabase.getMigrations(SharedPrefProvider(PreferenceManager - .getDefaultSharedPreferences(ApplicationProvider.getApplicationContext()))) - ) - .build() - // close the database and release any stream resources when the test finishes - helper.closeWhenFinished(database) - return database - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt deleted file mode 100644 index bbbfd83d..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories - -import io.github.wulkanowy.data.db.entities.Student -import org.threeten.bp.LocalDateTime - -fun getStudent(): Student { - return Student( - email = "test", - password = "test123", - schoolSymbol = "23", - scrapperBaseUrl = "fakelog.cf", - loginType = "AUTO", - isCurrent = true, - studentName = "", - schoolShortName = "", - schoolName = "", - studentId = 0, - classId = 1, - symbol = "", - registrationDate = LocalDateTime.now(), - className = "", - loginMode = "API", - certificateKey = "", - privateKey = "", - mobileBaseUrl = "", - userLoginId = 0, - isParent = false - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestInternetObservingStrategy.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestInternetObservingStrategy.kt deleted file mode 100644 index 7dc93c4a..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestInternetObservingStrategy.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.data.repositories - -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingStrategy -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.error.ErrorHandler -import io.reactivex.Observable -import io.reactivex.Single - -class TestInternetObservingStrategy : InternetObservingStrategy { - - override fun checkInternetConnectivity(host: String?, port: Int, timeoutInMs: Int, httpResponse: Int, errorHandler: ErrorHandler?): Single { - return Single.just(true) - } - - override fun observeInternetConnectivity(initialIntervalInMs: Int, intervalInMs: Int, host: String?, port: Int, timeoutInMs: Int, httpResponse: Int, errorHandler: ErrorHandler?): Observable { - return Observable.just(true) - } - - override fun getDefaultPingHost() = "localhost" -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt deleted file mode 100644 index c7ede6ae..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class AttendanceLocalTest { - - private lateinit var attendanceLocal: AttendanceLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build() - attendanceLocal = AttendanceLocal(testDb.attendanceDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - attendanceLocal.saveAttendance(listOf( - Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name), - Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name), - Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name) - )) - - val attendance = attendanceLocal - .getAttendance(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1), - of(2018, 9, 10), - of(2018, 9, 14) - ) - .blockingGet() - assertEquals(2, attendance.size) - assertEquals(attendance[0].date, of(2018, 9, 10)) - assertEquals(attendance[1].date, of(2018, 9, 14)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt deleted file mode 100644 index 50badc46..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class CompletedLessonsLocalTest { - - private lateinit var completedLessonsLocal: CompletedLessonsLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - completedLessonsLocal = CompletedLessonsLocal(testDb.completedLessonsDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - completedLessonsLocal.saveCompletedLessons(listOf( - getCompletedLesson(of(2018, 9, 10), 1), - getCompletedLesson(of(2018, 9, 14), 2), - getCompletedLesson(of(2018, 9, 17), 3) - )) - - val completed = completedLessonsLocal - .getCompletedLessons(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1), - of(2018, 9, 10), - of(2018, 9, 14) - ) - .blockingGet() - assertEquals(2, completed.size) - assertEquals(completed[0].date, of(2018, 9, 10)) - assertEquals(completed[1].date, of(2018, 9, 14)) - } - - private fun getCompletedLesson(date: LocalDate, number: Int): CompletedLesson { - return CompletedLesson(1, 2, date, number, "", "", "", "", "", "", "") - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt deleted file mode 100644 index 98dfc88e..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class ExamLocalTest { - - private lateinit var examLocal: ExamLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build() - examLocal = ExamLocal(testDb.examsDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - examLocal.saveExams(listOf( - Exam(1, 2, of(2018, 9, 10), now(), "", "", "", "", "", ""), - Exam(1, 2, of(2018, 9, 14), now(), "", "", "", "", "", ""), - Exam(1, 2, of(2018, 9, 17), now(), "", "", "", "", "", "") - )) - - val exams = examLocal - .getExams(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1), - of(2018, 9, 10), - of(2018, 9, 14) - ) - .blockingGet() - assertEquals(2, exams.size) - assertEquals(exams[0].date, of(2018, 9, 10)) - assertEquals(exams[1].date, of(2018, 9, 14)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt deleted file mode 100644 index 7233c306..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class GradeLocalTest { - - private lateinit var gradeLocal: GradeLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - gradeLocal = GradeLocal(testDb.gradeDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - gradeLocal.saveGrades(listOf( - createGradeLocal(5, 3.0, LocalDate.of(2018, 9, 10), "", 1), - createGradeLocal(4, 4.0, LocalDate.of(2019, 2, 27), "", 2), - createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2) - )) - - val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1) - - val grades = gradeLocal - .getGrades(semester) - .blockingGet() - - assertEquals(2, grades.size) - assertEquals(grades[0].date, LocalDate.of(2019, 2, 27)) - assertEquals(grades[1].date, LocalDate.of(2019, 2, 28)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt deleted file mode 100644 index f3c6b7a1..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt +++ /dev/null @@ -1,181 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import android.os.Build.VERSION_CODES.P -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy -import io.github.wulkanowy.sdk.Sdk -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.reactivex.Single -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate.of -import org.threeten.bp.LocalDateTime -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -@SdkSuppress(minSdkVersion = P) -@RunWith(AndroidJUnit4::class) -class GradeRepositoryTest { - - @MockK - private lateinit var mockSdk: Sdk - - private val settings = InternetObservingSettings.builder() - .strategy(TestInternetObservingStrategy()) - .build() - - @MockK - private lateinit var semesterMock: Semester - - @MockK - private lateinit var studentMock: Student - - private lateinit var gradeRemote: GradeRemote - - private lateinit var gradeLocal: GradeLocal - - private lateinit var testDb: AppDatabase - - @Before - fun initApi() { - MockKAnnotations.init(this) - testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - gradeLocal = GradeLocal(testDb.gradeDao) - gradeRemote = GradeRemote(mockSdk) - - every { studentMock.registrationDate } returns LocalDateTime.of(2019, 2, 27, 12, 0) - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun markOlderThanRegisterDateAsRead() { - every { mockSdk.getGrades(1) } returns Single.just(listOf( - createGradeApi(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), - createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), - createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), - createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") - )) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date } - - assertFalse { grades[0].isRead } - assertFalse { grades[1].isRead } - assertTrue { grades[2].isRead } - assertTrue { grades[3].isRead } - } - - @Test - fun mitigateOldGradesNotifications() { - gradeLocal.saveGrades(listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Jedna ocena"), - createGradeLocal(4, 4.0, of(2019, 2, 26), "Druga"), - createGradeLocal(3, 5.0, of(2019, 2, 27), "Trzecia") - )) - - every { mockSdk.getGrades(1) } returns Single.just(listOf( - createGradeApi(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), - createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), - createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), - createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") - )) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date } - - assertFalse { grades[0].isRead } - assertFalse { grades[1].isRead } - assertTrue { grades[2].isRead } - assertTrue { grades[3].isRead } - } - - @Test - fun subtractLocaleDuplicateGrades() { - gradeLocal.saveGrades(listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - every { mockSdk.getGrades(1) } returns Single.just(listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet() - - assertEquals(2, grades.size) - } - - @Test - fun subtractRemoteDuplicateGrades() { - gradeLocal.saveGrades(listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - every { mockSdk.getGrades(1) } returns Single.just(listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet() - - assertEquals(3, grades.size) - } - - @Test - fun emptyLocal() { - gradeLocal.saveGrades(listOf()) - - every { mockSdk.getGrades(1) } returns Single.just(listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet() - - assertEquals(3, grades.size) - } - - @Test - fun emptyRemote() { - gradeLocal.saveGrades(listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - )) - - every { mockSdk.getGrades(1) } returns Single.just(listOf()) - - val grades = GradeRepository(settings, gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true).blockingGet() - - assertEquals(0, grades.size) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt deleted file mode 100644 index 9146934b..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import org.threeten.bp.LocalDate -import io.github.wulkanowy.sdk.pojo.Grade as GradeRemote -import io.github.wulkanowy.data.db.entities.Grade as GradeLocal - -fun createGradeLocal(value: Int, weight: Double, date: LocalDate, desc: String, semesterId: Int = 1): GradeLocal { - return GradeLocal( - semesterId = semesterId, - studentId = 1, - modifier = .0, - teacher = "", - subject = "", - date = date, - color = "", - comment = "", - description = desc, - entry = "", - gradeSymbol = "", - value = value.toDouble(), - weight = "", - weightValue = weight - ) -} - -fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String): GradeRemote { - return GradeRemote( - subject = "", - color = "", - comment = "", - date = date, - description = desc, - entry = "", - modifier = .0, - symbol = "", - teacher = "", - value = value.toDouble(), - weight = weight.toString(), - weightValue = weight - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt deleted file mode 100644 index bd3635fe..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class GradeStatisticsLocalTest { - - private lateinit var gradeStatisticsLocal: GradeStatisticsLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndRead_subject() { - gradeStatisticsLocal.saveGradesStatistics(listOf( - getGradeStatistics("Matematyka", 2, 1), - getGradeStatistics("Fizyka", 1, 2) - )) - - val stats = gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Matematyka").blockingGet() - assertEquals(1, stats.size) - assertEquals(stats[0].subject, "Matematyka") - } - - @Test - fun saveAndRead_all() { - gradeStatisticsLocal.saveGradesStatistics(listOf( - getGradeStatistics("Matematyka", 2, 1), - getGradeStatistics("Chemia", 2, 1), - getGradeStatistics("Fizyka", 1, 2) - )) - - val stats = gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Wszystkie").blockingGet() - assertEquals(3, stats.size) - assertEquals(stats[0].subject, "Wszystkie") - assertEquals(stats[1].subject, "Matematyka") - assertEquals(stats[2].subject, "Chemia") - } - - @Test - fun saveAndRead_points() { - gradeStatisticsLocal.saveGradesPointsStatistics(listOf( - getGradePointsStatistics("Matematyka", 2, 1), - getGradePointsStatistics("Chemia", 2, 1), - getGradePointsStatistics("Fizyka", 1, 2) - )) - - val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka").blockingGet() - with(stats[0]) { - assertEquals(subject, "Matematyka") - assertEquals(others, 5.0) - assertEquals(student, 5.0) - } - } - - @Test - fun saveAndRead_subjectEmpty() { - gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) - - val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka").blockingGet() - assertEquals(null, stats) - } - - @Test - fun saveAndRead_allEmpty() { - gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) - - val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Wszystkie").blockingGet() - assertEquals(null, stats) - } - - private fun getSemester(): Semester { - return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1) - } - - private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics { - return GradeStatistics(studentId, semesterId, subject, 5, 5, false) - } - - private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics { - return GradePointsStatistics(studentId, semesterId, subject, 5.0, 5.0) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt deleted file mode 100644 index 65ef1fcb..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class LuckyNumberLocalTest { - - private lateinit var luckyNumberLocal: LuckyNumberLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - luckyNumberLocal = LuckyNumberLocal(testDb.luckyNumberDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - luckyNumberLocal.saveLuckyNumber(LuckyNumber(1, LocalDate.of(2019, 1, 20), 14)) - - val luckyNumber = luckyNumberLocal.getLuckyNumber(Student("", "", "", "", "", "", false, "", "", "", 1, 1, "", "", "", "", "", 1, false, now()), - LocalDate.of(2019, 1, 20) - ).blockingGet() - - assertEquals(1, luckyNumber.studentId) - assertEquals(LocalDate.of(2019, 1, 20), luckyNumber.date) - assertEquals(14, luckyNumber.luckyNumber) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt deleted file mode 100644 index 22578e41..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDateTime -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class RecipientLocalTest { - - private lateinit var recipientLocal: RecipientLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - recipientLocal = RecipientLocal(testDb.recipientDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - recipientLocal.saveRecipients(listOf( - Recipient(1, "2rPracownik", "Kowalski Jan", "Kowalski Jan [KJ] - Pracownik (Fake123456)", 3, 4, 2, "hash"), - Recipient(1, "3rPracownik", "Kowalska Karolina", "Kowalska Karolina [KK] - Pracownik (Fake123456)", 4, 4, 2, "hash"), - Recipient(1, "4rPracownik", "Krupa Stanisław", "Krupa Stanisław [KS] - Uczeń (Fake123456)", 5, 4, 1, "hash") - )) - - val recipients = recipientLocal.getRecipients( - Student("fakelog.cf", "AUTO", "", "", "", "", false, "", "", "", 1, 0, "", "", "", "", "", 1, true, LocalDateTime.now()), - 2, - ReportingUnit(1, 4, "", 0, "", emptyList()) - ).blockingGet() - - assertEquals(2, recipients.size) - assertEquals(1, recipients[0].studentId) - assertEquals("3rPracownik", recipients[1].realId) - assertEquals("Kowalski Jan", recipients[0].name) - assertEquals("Kowalska Karolina [KK] - Pracownik (Fake123456)", recipients[1].realName) - assertEquals(3, recipients[0].loginId) - assertEquals(4, recipients[1].unitId) - assertEquals(2, recipients[0].role) - assertEquals("hash", recipients[1].hash) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt deleted file mode 100644 index 530bfb3f..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.repositories.getStudent -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class StudentLocalTest { - - private lateinit var studentLocal: StudentLocal - - private lateinit var testDb: AppDatabase - - private val student = getStudent() - - @Before - fun createDb() { - val context = ApplicationProvider.getApplicationContext() - testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) - .build() - studentLocal = StudentLocal(testDb.studentDao, context) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - studentLocal.saveStudents(listOf(student)).blockingGet() - - val student = studentLocal.getCurrentStudent(true).blockingGet() - assertEquals("23", student.schoolSymbol) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt deleted file mode 100644 index aa35fe79..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import org.threeten.bp.LocalDateTime -import org.threeten.bp.LocalDateTime.now -import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal -import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote - -fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal { - return TimetableLocal( - studentId = 1, - diaryId = 2, - number = number, - start = start, - end = now(), - date = start.toLocalDate(), - subject = subject, - subjectOld = "", - group = "", - room = room, - roomOld = "", - teacher = teacher, - teacherOld = "", - info = "", - isStudentPlan = true, - changes = changes, - canceled = false - ) -} - -fun createTimetableRemote(start: LocalDateTime, number: Int = 1, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableRemote { - return TimetableRemote( - number = number, - start = start, - end = start.plusMinutes(45), - date = start.toLocalDate(), - subject = subject, - group = "", - room = room, - teacher = teacher, - info = "", - changes = changes, - canceled = false, - roomOld = "", - subjectOld = "", - teacherOld = "", - studentPlan = true - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt deleted file mode 100644 index a66e5843..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class TimetableLocalTest { - - private lateinit var timetableDb: TimetableLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - timetableDb = TimetableLocal(testDb.timetableDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - timetableDb.saveTimetable(listOf( - createTimetableLocal(of(2018, 9, 10, 0, 0, 0), 1), - createTimetableLocal(of(2018, 9, 14, 0, 0, 0), 1), - createTimetableLocal(of(2018, 9, 17, 0, 0, 0), 1) - )) - - val exams = timetableDb.getTimetable( - Semester(1, 2, "", 1, 1, 2019, LocalDate.now(), LocalDate.now(), 1, 1), - LocalDate.of(2018, 9, 10), - LocalDate.of(2018, 9, 14) - ).blockingGet() - - assertEquals(2, exams.size) - assertEquals(exams[0].date, LocalDate.of(2018, 9, 10)) - assertEquals(exams[1].date, LocalDate.of(2018, 9, 14)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt deleted file mode 100644 index fdf193a2..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ /dev/null @@ -1,150 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import android.os.Build.VERSION_CODES.P -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy -import io.github.wulkanowy.data.repositories.getStudent -import io.github.wulkanowy.sdk.Sdk -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.reactivex.Single -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime.of -import kotlin.test.assertEquals - -@SdkSuppress(minSdkVersion = P) -@RunWith(AndroidJUnit4::class) -class TimetableRepositoryTest { - - @MockK - private lateinit var mockSdk: Sdk - - private val settings = InternetObservingSettings.builder() - .strategy(TestInternetObservingStrategy()) - .build() - - private val student = getStudent() - - @MockK - private lateinit var semesterMock: Semester - - private lateinit var timetableRemote: TimetableRemote - - private lateinit var timetableLocal: TimetableLocal - - private lateinit var testDb: AppDatabase - - @Before - fun initApi() { - MockKAnnotations.init(this) - testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - timetableLocal = TimetableLocal(testDb.timetableDao) - timetableRemote = TimetableRemote(mockSdk) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 2 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun copyRoomToCompletedFromPrevious() { - timetableLocal.saveTimetable(listOf( - createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "123", "Przyroda"), - createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "321", "Religia"), - createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "213", "W-F"), - createTimetableLocal(of(2019, 3, 5, 10, 30),3, "213", "W-F", "Jan Kowalski") - )) - - every { mockSdk.getTimetable(any(), any()) } returns Single.just(listOf( - createTimetableRemote(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"), - createTimetableRemote(of(2019, 3, 5, 8, 50), 2, "", "Religia"), - createTimetableRemote(of(2019, 3, 5, 9, 40), 3, "", "W-F"), - createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F") - )) - - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) - .getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true) - .blockingGet() - - assertEquals(4, lessons.size) - assertEquals("123", lessons[0].room) - assertEquals("321", lessons[1].room) - assertEquals("213", lessons[2].room) - } - - @Test - fun copyTeacherToCompletedFromPrevious() { - timetableLocal.saveTimetable(listOf( - createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Wtorkowska", true), - createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), - - createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "Joanna Wtorkowska", false), - createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "Joanna Wtorkowska", false), - createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "Joanna Środowska", true), - createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "Joanna Środowska", true), - - createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "", false), - createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "", false), - createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "", true), - createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "", true) - )) - - every { mockSdk.getTimetable(any(), any()) } returns Single.just(listOf( - createTimetableRemote(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableRemote(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), - createTimetableRemote(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), - createTimetableRemote(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), - - createTimetableRemote(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "", false), - createTimetableRemote(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "", true), - createTimetableRemote(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "", false), - createTimetableRemote(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "", true), - - createTimetableRemote(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false), - createTimetableRemote(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), - createTimetableRemote(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), - createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) - )) - - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) - .getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true) - .blockingGet() - - assertEquals(12, lessons.size) - - assertEquals("Paweł Poniedziałkowski", lessons[0].teacher) - assertEquals("Jakub Wtorkowski", lessons[1].teacher) - assertEquals("Joanna Poniedziałkowska", lessons[2].teacher) - assertEquals("Joanna Wtorkowska", lessons[3].teacher) - - assertEquals("Joanna Wtorkowska", lessons[4].teacher) - assertEquals("", lessons[5].teacher) - assertEquals("", lessons[6].teacher) - assertEquals("", lessons[7].teacher) - - assertEquals("Paweł Środowski", lessons[8].teacher) - assertEquals("Paweł Czwartkowski", lessons[9].teacher) - assertEquals("Paweł Środowski", lessons[10].teacher) - assertEquals("Paweł Czwartkowski", lessons[11].teacher) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt index 0c47e6bb..1b0319f6 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt @@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class ScramblerTest { + private val scrambler = Scrambler(ApplicationProvider.getApplicationContext()) + @Test fun encryptDecryptTest() { - assertEquals("TEST", decrypt(encrypt("TEST", - ApplicationProvider.getApplicationContext()))) + assertEquals( + "TEST", scrambler.decrypt(scrambler.encrypt("TEST")) + ) } @Test fun emptyTextEncryptTest() { assertFailsWith { - decrypt("") + scrambler.decrypt("") } assertFailsWith { - encrypt("", ApplicationProvider.getApplicationContext()) + scrambler.encrypt("") } } @Test @SdkSuppress(minSdkVersion = 18) fun emptyKeyStoreTest() { - val text = encrypt("test", ApplicationProvider.getApplicationContext()) + val text = scrambler.encrypt("test") val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) keyStore.deleteEntry("wulkanowy_password") assertFailsWith { - decrypt(text) + scrambler.decrypt(text) } } } diff --git a/app/src/debug/res/drawable-hdpi/ic_stat_grade.png b/app/src/debug/res/drawable-hdpi/ic_stat_grade.png deleted file mode 100644 index 013b7ac4..00000000 Binary files a/app/src/debug/res/drawable-hdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/debug/res/drawable-hdpi/ic_stat_luckynumber.png b/app/src/debug/res/drawable-hdpi/ic_stat_luckynumber.png deleted file mode 100644 index 74a4d0c5..00000000 Binary files a/app/src/debug/res/drawable-hdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/debug/res/drawable-hdpi/ic_stat_message.png b/app/src/debug/res/drawable-hdpi/ic_stat_message.png deleted file mode 100644 index be41e343..00000000 Binary files a/app/src/debug/res/drawable-hdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/debug/res/drawable-hdpi/ic_stat_note.png b/app/src/debug/res/drawable-hdpi/ic_stat_note.png deleted file mode 100644 index 1992edf2..00000000 Binary files a/app/src/debug/res/drawable-hdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/debug/res/drawable-mdpi/ic_stat_grade.png b/app/src/debug/res/drawable-mdpi/ic_stat_grade.png deleted file mode 100644 index a5df2a35..00000000 Binary files a/app/src/debug/res/drawable-mdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/debug/res/drawable-mdpi/ic_stat_luckynumber.png b/app/src/debug/res/drawable-mdpi/ic_stat_luckynumber.png deleted file mode 100644 index 278ed2c6..00000000 Binary files a/app/src/debug/res/drawable-mdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/debug/res/drawable-mdpi/ic_stat_message.png b/app/src/debug/res/drawable-mdpi/ic_stat_message.png deleted file mode 100644 index 7327a02f..00000000 Binary files a/app/src/debug/res/drawable-mdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/debug/res/drawable-mdpi/ic_stat_note.png b/app/src/debug/res/drawable-mdpi/ic_stat_note.png deleted file mode 100644 index 2fb02098..00000000 Binary files a/app/src/debug/res/drawable-mdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xhdpi/ic_stat_grade.png b/app/src/debug/res/drawable-xhdpi/ic_stat_grade.png deleted file mode 100644 index c63f810f..00000000 Binary files a/app/src/debug/res/drawable-xhdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xhdpi/ic_stat_luckynumber.png b/app/src/debug/res/drawable-xhdpi/ic_stat_luckynumber.png deleted file mode 100644 index 4035ceba..00000000 Binary files a/app/src/debug/res/drawable-xhdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xhdpi/ic_stat_message.png b/app/src/debug/res/drawable-xhdpi/ic_stat_message.png deleted file mode 100644 index c4140be8..00000000 Binary files a/app/src/debug/res/drawable-xhdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xhdpi/ic_stat_note.png b/app/src/debug/res/drawable-xhdpi/ic_stat_note.png deleted file mode 100644 index 6b533c8e..00000000 Binary files a/app/src/debug/res/drawable-xhdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xxhdpi/ic_stat_grade.png b/app/src/debug/res/drawable-xxhdpi/ic_stat_grade.png deleted file mode 100644 index 13c26b77..00000000 Binary files a/app/src/debug/res/drawable-xxhdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xxhdpi/ic_stat_luckynumber.png b/app/src/debug/res/drawable-xxhdpi/ic_stat_luckynumber.png deleted file mode 100644 index da435745..00000000 Binary files a/app/src/debug/res/drawable-xxhdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xxhdpi/ic_stat_message.png b/app/src/debug/res/drawable-xxhdpi/ic_stat_message.png deleted file mode 100644 index 9d0fa781..00000000 Binary files a/app/src/debug/res/drawable-xxhdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/debug/res/drawable-xxhdpi/ic_stat_note.png b/app/src/debug/res/drawable-xxhdpi/ic_stat_note.png deleted file mode 100644 index 64da443f..00000000 Binary files a/app/src/debug/res/drawable-xxhdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_stat_grade.xml b/app/src/debug/res/drawable/ic_stat_grade.xml similarity index 100% rename from app/src/debug/res/drawable-anydpi-v24/ic_stat_grade.xml rename to app/src/debug/res/drawable/ic_stat_grade.xml diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_stat_luckynumber.xml b/app/src/debug/res/drawable/ic_stat_luckynumber.xml similarity index 100% rename from app/src/debug/res/drawable-anydpi-v24/ic_stat_luckynumber.xml rename to app/src/debug/res/drawable/ic_stat_luckynumber.xml diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_stat_message.xml b/app/src/debug/res/drawable/ic_stat_message.xml similarity index 100% rename from app/src/debug/res/drawable-anydpi-v24/ic_stat_message.xml rename to app/src/debug/res/drawable/ic_stat_message.xml diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_stat_note.xml b/app/src/debug/res/drawable/ic_stat_note.xml similarity index 100% rename from app/src/debug/res/drawable-anydpi-v24/ic_stat_note.xml rename to app/src/debug/res/drawable/ic_stat_note.xml diff --git a/app/src/debug/res/drawable/ic_stat_timetable.xml b/app/src/debug/res/drawable/ic_stat_timetable.xml new file mode 100644 index 00000000..ac99d4a8 --- /dev/null +++ b/app/src/debug/res/drawable/ic_stat_timetable.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml index 7dbec2cb..9c21d49d 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ - + - \ No newline at end of file + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 7dbec2cb..00000000 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 81e723ec..00000000 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 394b5707..00000000 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 365b4d66..00000000 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 463c089b..00000000 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 53d6f5bb..00000000 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml deleted file mode 100644 index c90641dd..00000000 --- a/app/src/debug/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Wulkanowy DEV - diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt new file mode 100644 index 00000000..3a3b5948 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import android.view.View +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@Suppress("unused") +class AdsHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val preferencesRepository: PreferencesRepository +) { + + val isMobileAdsSdkInitialized = MutableStateFlow(false) + val canShowAd = false + + fun initialize() { + preferencesRepository.isAdsEnabled = false + preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS + } + + @Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER") + suspend fun getDashboardTileAdBanner(width: Int): AdBanner { + throw IllegalStateException("Can't get ad banner (F-droid)") + } +} + +data class AdBanner(val view: View) diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt new file mode 100644 index 00000000..a3eed484 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.utils + +import android.app.Activity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@Suppress("UNUSED_PARAMETER") +class AnalyticsHelper @Inject constructor() { + + fun logEvent(name: String, vararg params: Pair) = Unit + fun setCurrentScreen(activity: Activity, name: String?) = Unit + fun popCurrentScreen(name: String?) = Unit +} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt similarity index 55% rename from app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt rename to app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt index 60dc6b56..5d58270d 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -2,15 +2,12 @@ package io.github.wulkanowy.utils -import android.content.Context import timber.log.Timber -fun initCrashlytics(context: Context, appInfo: AppInfo) {} - open class TimberTreeNoOp : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {} } -class CrashlyticsTree : TimberTreeNoOp() +class CrashLogTree : TimberTreeNoOp() -class CrashlyticsExceptionTree : TimberTreeNoOp() +class CrashLogExceptionTree : TimberTreeNoOp() diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt deleted file mode 100644 index 0b1274f1..00000000 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.wulkanowy.utils - -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class FirebaseAnalyticsHelper @Inject constructor() { - - @Suppress("UNUSED_PARAMETER") - fun logEvent(name: String, vararg params: Pair) { - // do nothing - } -} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt new file mode 100644 index 00000000..8615d975 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppReviewHelper.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.ui.modules.main.MainActivity +import javax.inject.Singleton +import javax.inject.Inject + +@Suppress("UNUSED_PARAMETER", "unused") +@Singleton +class InAppReviewHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + fun showInAppReview(activity: MainActivity) { + // do nothing + } +} \ No newline at end of file diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt new file mode 100644 index 00000000..51b22ec7 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.utils + +import android.view.View +import javax.inject.Inject + +class InAppUpdateHelper @Inject constructor() { + + lateinit var messageContainer: View + + fun checkAndInstallUpdates() {} + + fun onResume() {} +} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt new file mode 100644 index 00000000..7af68058 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class IntegrityHelper @Inject constructor() { + + @Suppress("UNUSED_PARAMETER") + fun getIntegrityToken(requestId: String): String? = null +} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt new file mode 100644 index 00000000..88f2598f --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper() diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt new file mode 100644 index 00000000..165a6204 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import android.view.View +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@Suppress("unused") +class AdsHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val preferencesRepository: PreferencesRepository +) { + val isMobileAdsSdkInitialized = MutableStateFlow(false) + val canShowAd = false + + fun initialize() { + preferencesRepository.isAdsEnabled = false + preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS + } + + @Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER") + suspend fun getDashboardTileAdBanner(width: Int): AdBanner { + throw IllegalStateException("Can't get ad banner (HMS)") + } +} + +data class AdBanner(val view: View) diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt new file mode 100644 index 00000000..1f78931a --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -0,0 +1,51 @@ +package io.github.wulkanowy.utils + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import com.huawei.agconnect.crash.AGConnectCrash +import com.huawei.hms.analytics.HiAnalytics +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.repositories.PreferencesRepository +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AnalyticsHelper @Inject constructor( + @ApplicationContext private val context: Context, + preferencesRepository: PreferencesRepository, + appInfo: AppInfo, +) { + + private val analytics by lazy { HiAnalytics.getInstance(context) } + + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + init { + if (!appInfo.isDebug) { + connectCrash.setUserId(preferencesRepository.installationId) + } + } + + fun logEvent(name: String, vararg params: Pair) { + Bundle().apply { + params.forEach { (key, value) -> + if (value == null) return@forEach + when (value) { + is String -> putString(key, value) + is Int -> putInt(key, value) + is Boolean -> putBoolean(key, value) + } + } + analytics.onEvent(name, this) + } + } + + fun setCurrentScreen(activity: Activity, name: String?) { + analytics.pageStart(name, activity::class.simpleName) + } + + fun popCurrentScreen(name: String?) { + analytics.pageEnd(name) + } +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt new file mode 100644 index 00000000..2b1f1d30 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.utils + +import android.util.Log +import com.huawei.agconnect.crash.AGConnectCrash +import fr.bipi.treessence.base.FormatterPriorityTree +import fr.bipi.treessence.common.StackTraceRecorder + +class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { + + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (skipLog(priority, tag, message, t)) return + + connectCrash.log(format(priority, tag, message)) + } +} + +class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter) { + + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (skipLog(priority, tag, message, t)) return + + if (t != null) { + connectCrash.recordException(t) + } else { + connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) + } + } +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt new file mode 100644 index 00000000..adb162fd --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/InAppReviewHelper.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.ui.modules.main.MainActivity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@Suppress("UNUSED_PARAMETER", "unused") +class InAppReviewHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + fun showInAppReview(activity: MainActivity) { + // do nothing + } +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt new file mode 100644 index 00000000..51b22ec7 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.utils + +import android.view.View +import javax.inject.Inject + +class InAppUpdateHelper @Inject constructor() { + + lateinit var messageContainer: View + + fun checkAndInstallUpdates() {} + + fun onResume() {} +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt new file mode 100644 index 00000000..7af68058 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class IntegrityHelper @Inject constructor() { + + @Suppress("UNUSED_PARAMETER") + fun getIntegrityToken(requestId: String): String? = null +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt new file mode 100644 index 00000000..88f2598f --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 42c75475..79d75bc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,29 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + android:resizeableActivity="true" + tools:ignore="DataExtractionRules,UnusedAttribute"> + tools:ignore="DiscouragedApi,LockedOrientationActivity"> @@ -33,13 +61,14 @@ android:name=".ui.modules.login.LoginActivity" android:configChanges="orientation|screenSize" android:label="@string/login_title" - android:theme="@style/WulkanowyTheme.NoActionBar" + android:theme="@style/WulkanowyTheme.Login" android:windowSoftInputMode="adjustResize" /> + android:theme="@style/WulkanowyTheme.NoActionBar" + android:windowSoftInputMode="adjustPan" /> @@ -58,6 +88,7 @@ @@ -68,6 +99,22 @@ + + + + + + + + + + @@ -91,12 +139,12 @@ android:resource="@xml/provider_widget_lucky_number" /> - + + - - + android:name="install_channel" + android:value="${install_channel}" /> - + android:resource="@drawable/ic_stat_all" /> + + diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index 40aff4c4..97ac9356 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -33,6 +33,30 @@ }, { "displayName": "Mateusz Idziejczak", - "githubUsername": "PanTajemnic" + "githubUsername": "Luncenok" + }, + { + "displayName": "Daniel Olczyk", + "githubUsername": "MRmlik12" + }, + { + "displayName": "Damian Czupryn", + "githubUsername": "Daxxxis" + }, + { + "displayName": "Kamil Studziński", + "githubUsername": "studzinskik" + }, + { + "displayName": "Tomasz F.", + "githubUsername": "Pengwius" + }, + { + "displayName": "Antoni Paduch", + "githubUsername": "janAte1" + }, + { + "displayName": "Kamil Wąsik", + "githubUsername": "JestemKamil" } ] diff --git a/app/src/main/assets/message-print-page.html b/app/src/main/assets/message-print-page.html new file mode 100644 index 00000000..8da7dec6 --- /dev/null +++ b/app/src/main/assets/message-print-page.html @@ -0,0 +1,94 @@ + + + + + %SUBJECT% | Wulkanowy + + + +

%SUBJECT%

+
+
+ %INFO% +
+ +
+
+

Treść wiadomości

+ %CONTENT% +
+ + diff --git a/app/src/main/assets/wulkanowy-logo-black.svg b/app/src/main/assets/wulkanowy-logo-black.svg new file mode 100644 index 00000000..9bfbe2c0 --- /dev/null +++ b/app/src/main/assets/wulkanowy-logo-black.svg @@ -0,0 +1,74 @@ + +image/svg+xml \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 96ec7cb8..38fade0a 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -1,37 +1,28 @@ package io.github.wulkanowy -import android.content.Context +import android.app.Application import android.util.Log.DEBUG import android.util.Log.INFO import android.util.Log.VERBOSE -import androidx.multidex.MultiDex +import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration -import com.jakewharton.threetenabp.AndroidThreeTen import com.yariksoffice.lingver.Lingver -import dagger.android.AndroidInjector -import dagger.android.support.DaggerApplication -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.utils.Log -import fr.bipi.tressence.file.FileLoggerTree -import io.github.wulkanowy.di.DaggerAppComponent -import io.github.wulkanowy.services.sync.SyncWorkerFactory +import dagger.hilt.android.HiltAndroidApp +import fr.bipi.treessence.file.FileLoggerTree +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.utils.ActivityLifecycleLogger +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.CrashlyticsExceptionTree -import io.github.wulkanowy.utils.CrashlyticsTree +import io.github.wulkanowy.utils.CrashLogExceptionTree +import io.github.wulkanowy.utils.CrashLogTree import io.github.wulkanowy.utils.DebugLogTree -import io.github.wulkanowy.utils.initCrashlytics -import io.reactivex.exceptions.UndeliverableException -import io.reactivex.plugins.RxJavaPlugins +import io.github.wulkanowy.utils.RemoteConfigHelper import timber.log.Timber -import java.io.IOException import javax.inject.Inject -class WulkanowyApp : DaggerApplication(), Configuration.Provider { - - @Inject - lateinit var workerFactory: SyncWorkerFactory +@HiltAndroidApp +class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var themeManager: ThemeManager @@ -39,54 +30,58 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider { @Inject lateinit var appInfo: AppInfo - override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base) - MultiDex.install(this) - } + @Inject + lateinit var preferencesRepository: PreferencesRepository + + @Inject + lateinit var analyticsHelper: AnalyticsHelper + + @Inject + lateinit var remoteConfigHelper: RemoteConfigHelper + + @Inject + lateinit var workerFactory: HiltWorkerFactory + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) + .build() override fun onCreate() { super.onCreate() - AndroidThreeTen.init(this) - RxJavaPlugins.setErrorHandler(::onError) - Lingver.init(this) + initializeAppLanguage() themeManager.applyDefaultTheme() - + remoteConfigHelper.initialize() initLogging() - initCrashlytics(this, appInfo) } private fun initLogging() { if (appInfo.isDebug) { - FlexibleAdapter.enableLogs(Log.Level.DEBUG) Timber.plant(DebugLogTree()) - Timber.plant(FileLoggerTree.Builder() - .withFileName("wulkanowy.%g.log") - .withDirName(applicationContext.filesDir.absolutePath) - .withFileLimit(10) - .withMinPriority(DEBUG) - .build() + Timber.plant( + FileLoggerTree.Builder() + .withFileName("wulkanowy.%g.log") + .withDirName(applicationContext.filesDir.absolutePath) + .withFileLimit(10) + .withMinPriority(DEBUG) + .build() ) } else { - Timber.plant(CrashlyticsExceptionTree()) - Timber.plant(CrashlyticsTree()) + Timber.plant(CrashLogExceptionTree()) + Timber.plant(CrashLogTree()) } registerActivityLifecycleCallbacks(ActivityLifecycleLogger()) } - private fun onError(error: Throwable) { - //RxJava's too deep stack traces may cause SOE on older android devices - val cause = error.cause - if (error is UndeliverableException && cause is IOException || cause is InterruptedException || cause is StackOverflowError) { - Timber.e(cause, "An undeliverable error occurred") - } else throw error - } + private fun initializeAppLanguage() { + Lingver.init(this) - override fun applicationInjector(): AndroidInjector { - return DaggerAppComponent.factory().create(this) + if (preferencesRepository.appLanguage == "system") { + Lingver.getInstance().setFollowSystemLocale(this) + analyticsHelper.logEvent("language", "startup" to appInfo.systemLanguage) + } else { + analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage) + } } - - override fun getWorkManagerConfiguration() = Configuration.Builder() - .setWorkerFactory(workerFactory) - .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) - .build() } diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt new file mode 100644 index 00000000..a21d56cc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -0,0 +1,246 @@ +package io.github.wulkanowy.data + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import com.chuckerteam.chucker.api.ChuckerCollector +import com.chuckerteam.chucker.api.ChuckerInterceptor +import com.chuckerteam.chucker.api.RetentionManager +import com.fredporciuncula.flow.preferences.FlowSharedPreferences +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import io.github.wulkanowy.data.api.services.SchoolsService +import io.github.wulkanowy.data.api.services.WulkanowyService +import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.utils.AppInfo +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.create +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal class DataModule { + + @Singleton + @Provides + fun provideChuckerCollector( + @ApplicationContext context: Context, + prefRepository: PreferencesRepository + ) = ChuckerCollector( + context = context, + showNotification = prefRepository.isDebugNotificationEnable, + retentionPeriod = RetentionManager.Period.ONE_HOUR + ) + + @Singleton + @Provides + fun provideChuckerInterceptor( + @ApplicationContext context: Context, + chuckerCollector: ChuckerCollector + ) = ChuckerInterceptor.Builder(context) + .collector(chuckerCollector) + .alwaysReadResponseBody(true) + .build() + + @Singleton + @Provides + fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient = + OkHttpClient.Builder() + .addNetworkInterceptor(chuckerInterceptor) + .addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BASIC + }) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + + @Singleton + @Provides + fun provideAdminMessageService( + okHttpClient: OkHttpClient, + json: Json, + appInfo: AppInfo + ): WulkanowyService = Retrofit.Builder() + .baseUrl(appInfo.messagesBaseUrl) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + .create() + + @Singleton + @Provides + fun provideSchoolsService( + okHttpClient: OkHttpClient, + json: Json, + appInfo: AppInfo, + ): SchoolsService = Retrofit.Builder() + .baseUrl(appInfo.schoolsBaseUrl) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + .create() + + @Singleton + @Provides + fun provideDatabase( + @ApplicationContext context: Context, + sharedPrefProvider: SharedPrefProvider, + appInfo: AppInfo + ) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo) + + @Singleton + @Provides + fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context) + + @Singleton + @Provides + fun provideFlowSharedPref(sharedPreferences: SharedPreferences) = + FlowSharedPreferences(sharedPreferences) + + @Singleton + @Provides + fun provideJson() = Json { + ignoreUnknownKeys = true + } + + @Singleton + @Provides + fun provideStudentDao(database: AppDatabase) = database.studentDao + + @Singleton + @Provides + fun provideSemesterDao(database: AppDatabase) = database.semesterDao + + @Singleton + @Provides + fun provideGradeDao(database: AppDatabase) = database.gradeDao + + @Singleton + @Provides + fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao + + @Singleton + @Provides + fun provideGradePartialStatisticsDao(database: AppDatabase) = database.gradePartialStatisticsDao + + @Singleton + @Provides + fun provideGradeSemesterStatisticsDao(database: AppDatabase) = + database.gradeSemesterStatisticsDao + + @Singleton + @Provides + fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatisticsDao + + @Singleton + @Provides + fun provideMessagesDao(database: AppDatabase) = database.messagesDao + + @Singleton + @Provides + fun provideMessageAttachmentsDao(database: AppDatabase) = database.messageAttachmentDao + + @Singleton + @Provides + fun provideExamDao(database: AppDatabase) = database.examsDao + + @Singleton + @Provides + fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao + + @Singleton + @Provides + fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao + + @Singleton + @Provides + fun provideTimetableDao(database: AppDatabase) = database.timetableDao + + @Singleton + @Provides + fun provideNoteDao(database: AppDatabase) = database.noteDao + + @Singleton + @Provides + fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao + + @Singleton + @Provides + fun provideSubjectDao(database: AppDatabase) = database.subjectDao + + @Singleton + @Provides + fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao + + @Singleton + @Provides + fun provideCompletedLessonsDao(database: AppDatabase) = database.completedLessonsDao + + @Singleton + @Provides + fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao + + @Singleton + @Provides + fun provideRecipientDao(database: AppDatabase) = database.recipientDao + + @Singleton + @Provides + fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao + + @Singleton + @Provides + fun provideTeacherDao(database: AppDatabase) = database.teacherDao + + @Singleton + @Provides + fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao + + @Singleton + @Provides + fun provideConferenceDao(database: AppDatabase) = database.conferenceDao + + @Singleton + @Provides + fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao + + @Singleton + @Provides + fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao + + @Singleton + @Provides + fun provideTimetableHeaderDao(database: AppDatabase) = database.timetableHeaderDao + + @Singleton + @Provides + fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao + + @Singleton + @Provides + fun provideNotificationDao(database: AppDatabase) = database.notificationDao + + @Singleton + @Provides + fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao + + @Singleton + @Provides + fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao + + @Singleton + @Provides + fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao +} diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt deleted file mode 100644 index 43c27c52..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ /dev/null @@ -1,159 +0,0 @@ -package io.github.wulkanowy.data - -import android.content.Context -import android.content.SharedPreferences -import android.content.res.AssetManager -import android.content.res.Resources -import androidx.preference.PreferenceManager -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy -import com.chuckerteam.chucker.api.ChuckerCollector -import com.chuckerteam.chucker.api.ChuckerInterceptor -import com.chuckerteam.chucker.api.RetentionManager -import dagger.Module -import dagger.Provides -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.sdk.Sdk -import timber.log.Timber -import javax.inject.Singleton - -@Module -internal class RepositoryModule { - - @Singleton - @Provides - fun provideInternetObservingSettings(): InternetObservingSettings { - return InternetObservingSettings.builder() - .strategy(WalledGardenInternetObservingStrategy()) - .build() - } - - @Singleton - @Provides - fun provideSdk(chuckerCollector: ChuckerCollector, context: Context): Sdk { - return Sdk().apply { - androidVersion = android.os.Build.VERSION.RELEASE - buildTag = android.os.Build.MODEL - setSimpleHttpLogger { Timber.d(it) } - - // for debug only - addInterceptor(ChuckerInterceptor(context, chuckerCollector), true) - } - } - - @Singleton - @Provides - fun provideChuckerCollector(context: Context, prefRepository: PreferencesRepository): ChuckerCollector { - return ChuckerCollector( - context = context, - showNotification = prefRepository.isDebugNotificationEnable, - retentionPeriod = RetentionManager.Period.ONE_HOUR - ) - } - - @Singleton - @Provides - fun provideDatabase(context: Context, sharedPrefProvider: SharedPrefProvider) = AppDatabase.newInstance(context, sharedPrefProvider) - - @Singleton - @Provides - fun provideResources(context: Context): Resources = context.resources - - @Singleton - @Provides - fun provideAssets(context: Context): AssetManager = context.assets - - @Singleton - @Provides - fun provideSharedPref(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - - @Singleton - @Provides - fun provideStudentDao(database: AppDatabase) = database.studentDao - - @Singleton - @Provides - fun provideSemesterDao(database: AppDatabase) = database.semesterDao - - @Singleton - @Provides - fun provideGradeDao(database: AppDatabase) = database.gradeDao - - @Singleton - @Provides - fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao - - @Singleton - @Provides - fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics - - @Singleton - @Provides - fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics - - @Singleton - @Provides - fun provideMessagesDao(database: AppDatabase) = database.messagesDao - - @Singleton - @Provides - fun provideMessageAttachmentsDao(database: AppDatabase) = database.messageAttachmentDao - - @Singleton - @Provides - fun provideExamDao(database: AppDatabase) = database.examsDao - - @Singleton - @Provides - fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao - - @Singleton - @Provides - fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao - - @Singleton - @Provides - fun provideTimetableDao(database: AppDatabase) = database.timetableDao - - @Singleton - @Provides - fun provideNoteDao(database: AppDatabase) = database.noteDao - - @Singleton - @Provides - fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao - - @Singleton - @Provides - fun provideSubjectDao(database: AppDatabase) = database.subjectDao - - @Singleton - @Provides - fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao - - @Singleton - @Provides - fun provideCompletedLessonsDao(database: AppDatabase) = database.completedLessonsDao - - @Singleton - @Provides - fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao - - @Singleton - @Provides - fun provideRecipientDao(database: AppDatabase) = database.recipientDao - - @Singleton - @Provides - fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao - - @Singleton - @Provides - fun provideTeacherDao(database: AppDatabase) = database.teacherDao - - @Singleton - @Provides - fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao -} diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt new file mode 100644 index 00000000..712a946f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -0,0 +1,285 @@ +package io.github.wulkanowy.data + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +sealed interface Resource { + /** + * The initial value of a resource flow. Indicates no data that is currently available to be shown, + * however with the expectation that the state will transition to another one soon. + */ + open class Loading : Resource + + /** + * A semi-loading state with some data available to be displayed (usually cached data loaded from + * the database). Still not the target state and it's expected to transition into another one soon. + */ + data class Intermediate(val data: T) : Loading() + + /** + * The happy-path target state. Data can either be: + * - loaded from the database - while it may seem like this case is already handled by the + * Intermediate state, the difference here is semantic. Cached data is returned as Intermediate + * when there's a API request in progress (or soon expected to be), however when there is no + * intention of immediately querying the API, the cached data is returned as a Success. + * - fetched from the API. + */ + data class Success(val data: T) : Resource + + /** + * Something bad happened and we were unable to get the requested data. This can be caused by + * a database error, a network error, or really just any other error. Upon receiving this state + * the UI can either: display a full screen error, or, when it has received any data previously, + * display a snack bar informing of the problem. + */ + data class Error(val error: Throwable) : Resource +} + +val Resource.dataOrNull: T? + get() = when (this) { + is Resource.Success -> this.data + is Resource.Intermediate -> this.data + else -> null + } + +val Resource.dataOrThrow: T + get() = when (this) { + is Resource.Success -> this.data + is Resource.Intermediate -> this.data + is Resource.Loading -> throw IllegalStateException("Resource is in loading state") + is Resource.Error -> throw this.error + } + +val Resource.errorOrNull: Throwable? + get() = when (this) { + is Resource.Error -> this.error + else -> null + } + +fun resourceFlow(block: suspend () -> T) = flow { + emit(Resource.Loading()) + emit(Resource.Success(block())) +}.catch { emit(Resource.Error(it)) } + +fun flatResourceFlow(block: suspend () -> Flow>) = flow { + emit(Resource.Loading()) + emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading }) +}.catch { emit(Resource.Error(it)) } + +fun Resource.mapData(block: (T) -> U) = when (this) { + is Resource.Success -> Resource.Success(block(this.data)) + is Resource.Intermediate -> Resource.Intermediate(block(this.data)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(this.error) +} + +/** + * Injects another flow into this flow's resource data. + */ +inline fun Flow>.combineWithResourceData( + flow: Flow, + crossinline block: suspend (T1, T2) -> R +): Flow> = + combine(flow) { resource, inject -> + when (resource) { + is Resource.Success -> Resource.Success(block(resource.data, inject)) + is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(resource.error) + } + } + +fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach { + val description = when (it) { + is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Loading -> "started" + is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Error -> "exception occurred: ${it.error}" + } + Timber.i("$name: $description") +} + +inline fun Flow>.mapResourceData(crossinline block: suspend (T) -> U) = map { + when (it) { + is Resource.Success -> Resource.Success(block(it.data)) + is Resource.Intermediate -> Resource.Intermediate(block(it.data)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(it.error) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +fun Flow>.flatMapResourceData( + inheritIntermediate: Boolean = true, block: suspend (T) -> Flow> +) = flatMapLatest { + when (it) { + is Resource.Success -> block(it.data) + is Resource.Intermediate -> block(it.data).map { newRes -> + if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data) + else newRes + } + + is Resource.Loading -> flowOf(Resource.Loading()) + is Resource.Error -> flowOf(Resource.Error(it.error)) + } +} + +fun Flow>.onResourceData(block: suspend (T) -> Unit) = onEach { + when (it) { + is Resource.Success -> block(it.data) + is Resource.Intermediate -> block(it.data) + is Resource.Error, + is Resource.Loading -> Unit + } +} + +fun Flow>.onResourceLoading(block: suspend () -> Unit) = onEach { + if (it is Resource.Loading) { + block() + } +} + +fun Flow>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Intermediate) { + block(it.data) + } +} + +fun Flow>.onResourceSuccess(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Success) { + block(it.data) + } +} + +fun Flow>.onResourceError(block: suspend (Throwable) -> Unit) = onEach { + if (it is Resource.Error) { + block(it.error) + } +} + +fun Flow>.onResourceNotLoading(block: suspend () -> Unit) = onEach { + if (it !is Resource.Loading) { + block() + } +} + +suspend fun Flow>.toFirstResult() = filter { it !is Resource.Loading }.first() + +suspend fun Flow>.waitForResult() = takeWhile { it is Resource.Loading }.collect() + +// Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired, +// use `debounceIntermediates` to alleviate this behavior. +inline fun combineResourceFlows(flows: Iterable>>): Flow>> = + combine(flows) { items -> + var isIntermediate = false + val data = mutableListOf() + for (item in items) { + when (item) { + is Resource.Success -> data.add(item.data) + is Resource.Intermediate -> { + isIntermediate = true + data.add(item.data) + } + + is Resource.Loading -> return@combine Resource.Loading() + is Resource.Error -> continue + } + } + if (data.isEmpty()) { + // All items have to be errors for this to happen, so just return the first one. + // mapData is functionally useless and exists only to satisfy the type checker + items.first().mapData { listOf(it) } + } else if (isIntermediate) { + Resource.Intermediate(data) + } else { + Resource.Success(data) + } + } + +@OptIn(FlowPreview::class) +fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = flow { + var wasIntermediate = false + + emitAll(this@debounceIntermediates.debounce { + if (it is Resource.Intermediate) { + if (!wasIntermediate) { + wasIntermediate = true + Duration.ZERO + } else { + timeout + } + } else { + wasIntermediate = false + Duration.ZERO + } + }) +} + + +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + crossinline isResultEmpty: (OutputType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend () -> ApiType, + crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit, + crossinline shouldFetch: (OutputType) -> Boolean = { true }, + crossinline filterResult: (OutputType) -> OutputType = { it } +) = networkBoundResource( + mutex = mutex, + isResultEmpty = isResultEmpty, + query = query, + fetch = fetch, + saveFetchResult = saveFetchResult, + shouldFetch = shouldFetch, + mapResult = filterResult +) + +@JvmName("networkBoundResourceWithMap") +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + crossinline isResultEmpty: (OutputType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend () -> ApiType, + crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit, + crossinline shouldFetch: (DatabaseType) -> Boolean = { true }, + crossinline mapResult: (DatabaseType) -> OutputType, +) = flow { + emit(Resource.Loading()) + + val data = query().first() + if (shouldFetch(data)) { + emit(Resource.Intermediate(data)) + + try { + val newData = fetch() + mutex.withLock { saveFetchResult(query().first(), newData) } + } catch (throwable: Throwable) { + emit(Resource.Error(throwable)) + return@flow + } + } + + emitAll(query().map { Resource.Success(it) }) +} + .mapResourceData { mapResult(it) } + .filterNot { it is Resource.Intermediate && isResultEmpty(it.data) } diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt new file mode 100644 index 00000000..dbd34cee --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data + +import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsEduOne +import io.github.wulkanowy.data.repositories.WulkanowyRepository +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.RemoteConfigHelper +import io.github.wulkanowy.utils.WebkitCookieManagerProxy +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WulkanowySdkFactory @Inject constructor( + private val chuckerInterceptor: ChuckerInterceptor, + private val remoteConfig: RemoteConfigHelper, + private val webkitCookieManagerProxy: WebkitCookieManagerProxy, + private val studentDb: StudentDao, + private val wulkanowyRepository: WulkanowyRepository, +) { + + private val eduOneMutex = Mutex() + private val migrationFailedStudentIds = mutableSetOf() + + private val sdk = Sdk().apply { + androidVersion = android.os.Build.VERSION.RELEASE + buildTag = android.os.Build.MODEL + userAgentTemplate = remoteConfig.userAgentTemplate + setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(webkitCookieManagerProxy) + + // for debug only + addInterceptor(chuckerInterceptor, network = true) + } + + fun createBase() = sdk + + suspend fun create(): Sdk { + val mapping = wulkanowyRepository.getMapping() + + return createBase().apply { + if (mapping != null) { + endpointsMapping = mapping.endpoints + vTokenMapping = mapping.vTokens + vTokenSchemeMapping = mapping.vTokenScheme + } + } + } + + suspend fun create(student: Student, semester: Semester? = null): Sdk { + val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student) + return buildSdk(student, semester, overrideIsEduOne) + } + + private suspend fun buildSdk( + student: Student, + semester: Semester?, + isStudentEduOne: Boolean + ): Sdk { + return create().apply { + email = student.email + password = student.password + symbol = student.symbol + schoolSymbol = student.schoolSymbol + studentId = student.studentId + classId = student.classId + emptyCookieJarInterceptor = true + isEduOne = isStudentEduOne + + if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { + mobileBaseUrl = student.mobileBaseUrl + } else { + scrapperBaseUrl = student.scrapperBaseUrl + domainSuffix = student.scrapperDomainSuffix + loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) + } + + mode = Sdk.Mode.valueOf(student.loginMode) + mobileBaseUrl = student.mobileBaseUrl + keyId = student.certificateKey + privatePem = student.privateKey + + if (semester != null) { + diaryId = semester.diaryId + kindergartenDiaryId = semester.kindergartenDiaryId + schoolYear = semester.schoolYear + unitId = semester.unitId + } + } + } + + private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean { + if (student.isEduOne != null) return student.isEduOne + + if (student.id in migrationFailedStudentIds) { + Timber.i("Migration eduOne: skipping because of previous failure") + return false + } + + eduOneMutex.withLock { + if (student.id in migrationFailedStudentIds) { + Timber.i("Migration eduOne: skipping because of previous failure") + return false + } + + val studentFromDatabase = studentDb.loadById(student.id) + if (studentFromDatabase?.isEduOne != null) { + Timber.i("Migration eduOne: already done") + return studentFromDatabase.isEduOne + } + + Timber.i("Migration eduOne: flag missing. Running migration...") + val initializedSdk = buildSdk( + student = student, + semester = null, + isStudentEduOne = false, // doesn't matter + ) + val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Migration eduOne: can't get current student") } + .getOrNull() + + if (newCurrentStudent == null) { + Timber.i("Migration eduOne: failed, so skipping") + migrationFailedStudentIds.add(student.id) + return false + } + + Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}") + + val studentIsEduOne = StudentIsEduOne( + id = student.id, + isEduOne = newCurrentStudent.isEduOne + ) + studentDb.update(studentIsEduOne) + return newCurrentStudent.isEduOne + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt b/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt new file mode 100644 index 00000000..c4667a47 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.data.api.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Mapping( + + @SerialName("endpoints") + val endpoints: Map>>, + + @SerialName("vTokens") + val vTokens: Map>>, + + @SerialName("vTokenScheme") + val vTokenScheme: Map> = emptyMap(), +) diff --git a/app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt b/app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt new file mode 100644 index 00000000..07fbeb89 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.api.services + +import io.github.wulkanowy.data.pojos.IntegrityRequest +import io.github.wulkanowy.data.pojos.LoginEvent +import retrofit2.http.Body +import retrofit2.http.POST +import javax.inject.Singleton + +@Singleton +interface SchoolsService { + + @POST("/log/loginEvent") + suspend fun logLoginEvent(@Body request: IntegrityRequest) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt b/app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt new file mode 100644 index 00000000..161e93d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.api.services + +import io.github.wulkanowy.data.api.models.Mapping +import io.github.wulkanowy.data.db.entities.AdminMessage +import retrofit2.http.GET +import javax.inject.Singleton + +@Singleton +interface WulkanowyService { + + @GET("/v1.json") + suspend fun getAdminMessages(): List + + @GET("/mapping2.json") + suspend fun getMapping(): Mapping +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 762c52f8..f23c79de 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -1,56 +1,76 @@ package io.github.wulkanowy.data.db import android.content.Context +import androidx.room.AutoMigration import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.TypeConverters -import androidx.room.migration.Migration +import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao import io.github.wulkanowy.data.db.dao.NoteDao +import io.github.wulkanowy.data.db.dao.NotificationDao import io.github.wulkanowy.data.db.dao.RecipientDao -import io.github.wulkanowy.data.db.dao.ReportingUnitDao +import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.dao.StudentInfoDao import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.dao.TeacherDao +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao +import io.github.wulkanowy.data.db.dao.TimetableHeaderDao +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.MutedMessageSender import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentInfo import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.db.migrations.Migration10 import io.github.wulkanowy.data.db.migrations.Migration11 import io.github.wulkanowy.data.db.migrations.Migration12 @@ -68,13 +88,43 @@ import io.github.wulkanowy.data.db.migrations.Migration22 import io.github.wulkanowy.data.db.migrations.Migration23 import io.github.wulkanowy.data.db.migrations.Migration24 import io.github.wulkanowy.data.db.migrations.Migration25 +import io.github.wulkanowy.data.db.migrations.Migration26 +import io.github.wulkanowy.data.db.migrations.Migration27 +import io.github.wulkanowy.data.db.migrations.Migration28 +import io.github.wulkanowy.data.db.migrations.Migration29 import io.github.wulkanowy.data.db.migrations.Migration3 +import io.github.wulkanowy.data.db.migrations.Migration30 +import io.github.wulkanowy.data.db.migrations.Migration31 +import io.github.wulkanowy.data.db.migrations.Migration32 +import io.github.wulkanowy.data.db.migrations.Migration33 +import io.github.wulkanowy.data.db.migrations.Migration34 +import io.github.wulkanowy.data.db.migrations.Migration35 +import io.github.wulkanowy.data.db.migrations.Migration36 +import io.github.wulkanowy.data.db.migrations.Migration37 +import io.github.wulkanowy.data.db.migrations.Migration38 +import io.github.wulkanowy.data.db.migrations.Migration39 import io.github.wulkanowy.data.db.migrations.Migration4 +import io.github.wulkanowy.data.db.migrations.Migration40 +import io.github.wulkanowy.data.db.migrations.Migration41 +import io.github.wulkanowy.data.db.migrations.Migration42 +import io.github.wulkanowy.data.db.migrations.Migration43 +import io.github.wulkanowy.data.db.migrations.Migration44 +import io.github.wulkanowy.data.db.migrations.Migration46 +import io.github.wulkanowy.data.db.migrations.Migration49 import io.github.wulkanowy.data.db.migrations.Migration5 +import io.github.wulkanowy.data.db.migrations.Migration50 +import io.github.wulkanowy.data.db.migrations.Migration51 +import io.github.wulkanowy.data.db.migrations.Migration53 +import io.github.wulkanowy.data.db.migrations.Migration54 +import io.github.wulkanowy.data.db.migrations.Migration55 +import io.github.wulkanowy.data.db.migrations.Migration57 +import io.github.wulkanowy.data.db.migrations.Migration58 import io.github.wulkanowy.data.db.migrations.Migration6 +import io.github.wulkanowy.data.db.migrations.Migration63 import io.github.wulkanowy.data.db.migrations.Migration7 import io.github.wulkanowy.data.db.migrations.Migration8 import io.github.wulkanowy.data.db.migrations.Migration9 +import io.github.wulkanowy.utils.AppInfo import javax.inject.Singleton @Singleton @@ -88,8 +138,9 @@ import javax.inject.Singleton AttendanceSummary::class, Grade::class, GradeSummary::class, - GradeStatistics::class, + GradePartialStatistics::class, GradePointsStatistics::class, + GradeSemesterStatistics::class, Message::class, MessageAttachment::class, Note::class, @@ -97,11 +148,36 @@ import javax.inject.Singleton Subject::class, LuckyNumber::class, CompletedLesson::class, - ReportingUnit::class, + Mailbox::class, Recipient::class, MobileDevice::class, Teacher::class, - School::class + School::class, + Conference::class, + TimetableAdditional::class, + StudentInfo::class, + TimetableHeader::class, + SchoolAnnouncement::class, + Notification::class, + AdminMessage::class, + MutedMessageSender::class, + GradeDescriptive::class, + ], + autoMigrations = [ + AutoMigration(from = 44, to = 45), + AutoMigration(from = 46, to = 47), + AutoMigration(from = 47, to = 48), + AutoMigration(from = 51, to = 52), + AutoMigration(from = 54, to = 55, spec = Migration55::class), + AutoMigration(from = 55, to = 56), + AutoMigration(from = 56, to = 57, spec = Migration57::class), + AutoMigration(from = 57, to = 58, spec = Migration58::class), + AutoMigration(from = 58, to = 59), + AutoMigration(from = 59, to = 60), + AutoMigration(from = 60, to = 61), + AutoMigration(from = 61, to = 62), + AutoMigration(from = 62, to = 63, spec = Migration63::class), + AutoMigration(from = 63, to = 64), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -110,45 +186,70 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 25 + const val VERSION_SCHEMA = 64 - fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { - return arrayOf( - Migration2(), - Migration3(), - Migration4(), - Migration5(), - Migration6(), - Migration7(), - Migration8(), - Migration9(), - Migration10(), - Migration11(), - Migration12(), - Migration13(), - Migration14(), - Migration15(), - Migration16(), - Migration17(), - Migration18(), - Migration19(sharedPrefProvider), - Migration20(), - Migration21(), - Migration22(), - Migration23(), - Migration24(), - Migration25() - ) - } + fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( + Migration2(), + Migration3(), + Migration4(), + Migration5(), + Migration6(), + Migration7(), + Migration8(), + Migration9(), + Migration10(), + Migration11(), + Migration12(), + Migration13(), + Migration14(), + Migration15(), + Migration16(), + Migration17(), + Migration18(), + Migration19(sharedPrefProvider), + Migration20(), + Migration21(), + Migration22(), + Migration23(), + Migration24(), + Migration25(), + Migration26(), + Migration27(), + Migration28(), + Migration29(), + Migration30(), + Migration31(), + Migration32(), + Migration33(), + Migration34(), + Migration35(appInfo), + Migration36(), + Migration37(), + Migration38(), + Migration39(), + Migration40(), + Migration41(sharedPrefProvider), + Migration42(), + Migration43(), + Migration44(), + Migration46(), + Migration49(), + Migration50(), + Migration51(), + Migration53(), + Migration54(), + ) - fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase { - return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") - .setJournalMode(TRUNCATE) - .fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1) - .fallbackToDestructiveMigrationOnDowngrade() - .addMigrations(*getMigrations(sharedPrefProvider)) - .build() - } + fun newInstance( + context: Context, + sharedPrefProvider: SharedPrefProvider, + appInfo: AppInfo + ) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") + .setJournalMode(TRUNCATE) + .fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1) + .fallbackToDestructiveMigrationOnDowngrade() + .addMigrations(*getMigrations(sharedPrefProvider, appInfo)) + .build() } abstract val studentDao: StudentDao @@ -167,9 +268,11 @@ abstract class AppDatabase : RoomDatabase() { abstract val gradeSummaryDao: GradeSummaryDao - abstract val gradeStatistics: GradeStatisticsDao + abstract val gradePartialStatisticsDao: GradePartialStatisticsDao - abstract val gradePointsStatistics: GradePointsStatisticsDao + abstract val gradePointsStatisticsDao: GradePointsStatisticsDao + + abstract val gradeSemesterStatisticsDao: GradeSemesterStatisticsDao abstract val messagesDao: MessagesDao @@ -185,7 +288,7 @@ abstract class AppDatabase : RoomDatabase() { abstract val completedLessonsDao: CompletedLessonsDao - abstract val reportingUnitDao: ReportingUnitDao + abstract val mailboxDao: MailboxDao abstract val recipientDao: RecipientDao @@ -194,4 +297,22 @@ abstract class AppDatabase : RoomDatabase() { abstract val teacherDao: TeacherDao abstract val schoolDao: SchoolDao + + abstract val conferenceDao: ConferenceDao + + abstract val timetableAdditionalDao: TimetableAdditionalDao + + abstract val studentInfoDao: StudentInfoDao + + abstract val timetableHeaderDao: TimetableHeaderDao + + abstract val schoolAnnouncementDao: SchoolAnnouncementDao + + abstract val notificationDao: NotificationDao + + abstract val adminMessagesDao: AdminMessageDao + + abstract val mutedMessageSendersDao: MutedMessageSendersDao + + abstract val gradeDescriptiveDao: GradeDescriptiveDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt index 294f73d3..7bc8d12a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt @@ -1,37 +1,37 @@ package io.github.wulkanowy.data.db import androidx.room.TypeConverter -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import org.threeten.bp.DateTimeUtils -import org.threeten.bp.Instant -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime -import org.threeten.bp.Month -import org.threeten.bp.ZoneOffset -import java.util.Date +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.utils.toTimestamp +import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.* +import java.util.* +import java.time.Instant +import java.time.LocalDate +import java.time.Month +import java.time.ZoneOffset +import java.util.* class Converters { - @TypeConverter - fun timestampToDate(value: Long?): LocalDate? = value?.run { - DateTimeUtils.toInstant(Date(value)).atZone(ZoneOffset.UTC).toLocalDate() - } + private val json = Json @TypeConverter - fun dateToTimestamp(date: LocalDate?): Long? { - return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli() - } + fun timestampToLocalDate(value: Long?): LocalDate? = + value?.let(::Date)?.toInstant()?.atZone(ZoneOffset.UTC)?.toLocalDate() @TypeConverter - fun timestampToTime(value: Long?): LocalDateTime? = value?.let { - LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC) - } + fun dateToTimestamp(date: LocalDate?): Long? = date?.toTimestamp() @TypeConverter - fun timeToTimestamp(date: LocalDateTime?): Long? { - return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() - } + fun instantToTimestamp(instant: Instant?): Long? = instant?.toEpochMilli() + + @TypeConverter + fun timestampToInstant(timestamp: Long?): Instant? = timestamp?.let(Instant::ofEpochMilli) @TypeConverter fun monthToInt(month: Month?) = month?.value @@ -40,22 +40,38 @@ class Converters { fun intToMonth(value: Int?) = value?.let { Month.of(it) } @TypeConverter - fun intListToGson(list: List): String { - return Gson().toJson(list) + fun intListToJson(list: List): String { + return json.encodeToString(list) } @TypeConverter - fun gsonToIntList(value: String): List { - return Gson().fromJson(value, object : TypeToken>() {}.type) + fun jsonToIntList(value: String): List { + return json.decodeFromString(value) } @TypeConverter - fun stringPairListToGson(list: List>): String { - return Gson().toJson(list) + fun stringPairListToJson(list: List>): String { + return json.encodeToString(list) } @TypeConverter - fun gsonToStringPairList(value: String): List> { - return Gson().fromJson(value, object : TypeToken>>() {}.type) + fun jsonToStringPairList(value: String): List> { + return try { + json.decodeFromString(value) + } catch (e: SerializationException) { + emptyList() // handle errors from old gson Pair serialized data + } } + + @TypeConverter + fun destinationToString(destination: Destination) = json.encodeToString(destination) + + @TypeConverter + fun stringToDestination(destination: String): Destination = json.decodeFromString(destination) + + @TypeConverter + fun messageTypesToString(types: List): String = json.encodeToString(types) + + @TypeConverter + fun stringToMessageTypes(text: String): List = json.decodeFromString(text) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt index 4a4aaf74..4929f046 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt @@ -6,7 +6,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) { +class SharedPrefProvider @Inject constructor( + private val sharedPref: SharedPreferences +) { companion object { const val APP_VERSION_CODE_KEY = "app_version_code" @@ -18,9 +20,18 @@ class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPrefe fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue) - fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue + fun getString(key: String) = sharedPref.getString(key, null) - fun putString(key: String, value: String, sync: Boolean = false) { + fun getString(key: String, defaultValue: String): String = + sharedPref.getString(key, defaultValue) ?: defaultValue + + fun getBoolean(key: String, defaultValue: Boolean): Boolean = + sharedPref.getBoolean(key, defaultValue) + + fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = + sharedPref.edit(sync) { putBoolean(key, value) } + + fun putString(key: String, value: String?, sync: Boolean = false) { sharedPref.edit(sync) { putString(key, value) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt new file mode 100644 index 00000000..6c8d7e47 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.AdminMessage +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface AdminMessageDao : BaseDao { + + @Query("SELECT * FROM AdminMessages") + fun loadAll(): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt index 3eb57473..7d20c9a7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt @@ -3,14 +3,20 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Attendance -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @Dao interface AttendanceDao : BaseDao { - @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end") + fun loadAll( + diaryId: Int, + studentId: Int, + start: LocalDate, + end: LocalDate + ): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt index fd58533f..4218855c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt @@ -3,11 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow @Dao interface AttendanceSummaryDao : BaseDao { @Query("SELECT * FROM AttendanceSummary WHERE diary_id = :diaryId AND student_id = :studentId AND subject_id = :subjectId") - fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Maybe> + fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt index 32dbadb8..937e9824 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt @@ -2,16 +2,24 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Delete import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Transaction import androidx.room.Update interface BaseDao { - @Insert - fun insertAll(items: List): List + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(items: List): List @Update - fun updateAll(items: List) + suspend fun updateAll(items: List) @Delete - fun deleteAll(items: List) + suspend fun deleteAll(items: List) + + @Transaction + suspend fun removeOldAndSaveNew(oldItems: List, newItems: List) { + deleteAll(oldItems) + insertAll(newItems) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt index e13e569b..097ad7c8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @@ -12,5 +12,5 @@ import javax.inject.Singleton interface CompletedLessonsDao : BaseDao { @Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(studentId: Int, diaryId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt new file mode 100644 index 00000000..ca9da9ea --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.Conference +import kotlinx.coroutines.flow.Flow +import java.time.Instant +import javax.inject.Singleton + +@Dao +@Singleton +interface ConferenceDao : BaseDao { + + @Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate") + fun loadAll(diaryId: Int, studentId: Int, startDate: Instant): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt index ca6b32df..311eeb9c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Exam -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @@ -12,5 +12,5 @@ import javax.inject.Singleton interface ExamDao : BaseDao { @Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt index c74d1937..ff332618 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Grade -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -11,6 +11,9 @@ import javax.inject.Singleton interface GradeDao : BaseDao { @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") - fun loadAll(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Flow> + @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId " + + "AND entry NOT IN(:censoredEntries)") + fun loadAllCensored(semesterId: Int, studentId: Int, censoredEntries: Array): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt new file mode 100644 index 00000000..6282c080 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface GradeDescriptiveDao : BaseDao { + + @Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt new file mode 100644 index 00000000..bce6ce57 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import kotlinx.coroutines.flow.Flow + +@Dao +interface GradePartialStatisticsDao : BaseDao { + + @Query("SELECT * FROM GradePartialStatistics WHERE student_id = :studentId AND semester_id = :semesterId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt index da182866..e8074f00 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -11,8 +11,8 @@ import javax.inject.Singleton interface GradePointsStatisticsDao : BaseDao { @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName") - fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe> + fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Flow> @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId") - fun loadAll(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt new file mode 100644 index 00000000..09ae8171 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import kotlinx.coroutines.flow.Flow + +@Dao +interface GradeSemesterStatisticsDao : BaseDao { + + @Query("SELECT * FROM GradeSemesterStatistics WHERE student_id = :studentId AND semester_id = :semesterId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt deleted file mode 100644 index 6faa35d0..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.wulkanowy.data.db.dao - -import androidx.room.Dao -import androidx.room.Query -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.reactivex.Maybe -import javax.inject.Singleton - -@Singleton -@Dao -interface GradeStatisticsDao : BaseDao { - - @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester") - fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Maybe> - - @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester") - fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Maybe> -} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt index 1165ef07..fc9ad66e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradeSummary -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -11,5 +11,5 @@ import javax.inject.Singleton interface GradeSummaryDao : BaseDao { @Query("SELECT * FROM GradesSummary WHERE student_id = :studentId AND semester_id = :semesterId") - fun loadAll(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt index 1947a0df..2092de49 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Homework -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @@ -12,5 +12,5 @@ import javax.inject.Singleton interface HomeworkDao : BaseDao { @Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt index f16c28d9..d9aa2436 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @@ -12,5 +12,8 @@ import javax.inject.Singleton interface LuckyNumberDao : BaseDao { @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") - fun load(studentId: Int, date: LocalDate): Maybe + fun load(studentId: Int, date: LocalDate): Flow + + @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date >= :start AND date <= :end") + fun getAll(studentId: Int, start: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt new file mode 100644 index 00000000..084192a0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.Mailbox +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface MailboxDao : BaseDao { + + @Query("SELECT * FROM Mailboxes WHERE email = :email") + suspend fun loadAll(email: String): List + + @Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId") + fun loadAll(email: String, symbol: String, schoolId: String): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessageAttachmentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessageAttachmentDao.kt index 3c511a27..b69083a1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessageAttachmentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessageAttachmentDao.kt @@ -9,5 +9,5 @@ import io.github.wulkanowy.data.db.entities.MessageAttachment interface MessageAttachmentDao : BaseDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAttachments(items: List): List + suspend fun insertAttachments(items: List): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt index 7a69e270..11e6da1e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt @@ -5,19 +5,26 @@ import androidx.room.Query import androidx.room.Transaction import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.reactivex.Maybe -import io.reactivex.Single +import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor +import kotlinx.coroutines.flow.Flow @Dao interface MessagesDao : BaseDao { + @Transaction + @Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey") + fun loadMessageWithAttachment(messageGlobalKey: String): Flow @Transaction - @Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId") - fun loadMessageWithAttachment(studentId: Int, messageId: Int): Single + @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") + fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow> - @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC") - fun loadAll(studentId: Int, folder: Int): Maybe> + @Transaction + @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC") + fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow> - @Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC") - fun loadDeleted(studentId: Int): Maybe> + @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") + fun loadAll(mailboxKey: String, folder: Int): Flow> + + @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC") + fun loadAll(folder: Int, email: String): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt index b05b2d9c..5ddb4dd0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt @@ -3,11 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.MobileDevice -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow @Dao interface MobileDeviceDao : BaseDao { - @Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC") - fun loadAll(studentId: Int): Maybe> + @Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt new file mode 100644 index 00000000..0a866401 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.MutedMessageSender + +@Dao +interface MutedMessageSendersDao : BaseDao { + + @Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author") + suspend fun checkMute(author: String): Boolean + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertMute(mute: MutedMessageSender): Long + + @Query("DELETE FROM MutedMessageSenders WHERE author = :author") + suspend fun deleteMute(author: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt index ea2fc6eb..98de5ba3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt @@ -3,13 +3,12 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Note -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @Dao interface NoteDao : BaseDao { - @Query("SELECT * FROM Notes WHERE student_id = :studentId") - fun loadAll(studentId: Int): Maybe> + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/NotificationDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/NotificationDao.kt new file mode 100644 index 00000000..c5ae21bc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/NotificationDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.Notification +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface NotificationDao : BaseDao { + + @Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1") + fun loadAll(studentId: Long): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt index afb941b1..1956261e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt @@ -2,14 +2,14 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query +import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Recipient -import io.reactivex.Maybe import javax.inject.Singleton @Singleton @Dao interface RecipientDao : BaseDao { - @Query("SELECT * FROM Recipients WHERE student_id = :studentId AND role = :role AND unit_id = :unitId") - fun load(studentId: Int, role: Int, unitId: Int): Maybe> + @Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey") + suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ReportingUnitDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ReportingUnitDao.kt deleted file mode 100644 index 6ddfd494..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ReportingUnitDao.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.wulkanowy.data.db.dao - -import androidx.room.Dao -import androidx.room.Query -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.reactivex.Maybe -import javax.inject.Singleton - -@Singleton -@Dao -interface ReportingUnitDao : BaseDao { - - @Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId") - fun load(studentId: Int): Maybe> - - @Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId AND real_id = :unitId") - fun loadOne(studentId: Int, unitId: Int): Maybe -} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt new file mode 100644 index 00000000..64d49bce --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Dao +@Singleton +interface SchoolAnnouncementDao : BaseDao { + + @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt index e9bd6755..f39791f6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.School -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -11,5 +11,5 @@ import javax.inject.Singleton interface SchoolDao : BaseDao { @Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId") - fun load(studentId: Int, classId: Int): Maybe + fun load(studentId: Int, classId: Int): Flow } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt index 654b80f3..bf9a34d0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt @@ -1,15 +1,19 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe import javax.inject.Singleton @Singleton @Dao interface SemesterDao : BaseDao { - @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId") - fun loadAll(studentId: Int, classId: Int): Maybe> + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertSemesters(items: List): List + + @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)") + suspend fun loadAll(studentId: Int, classId: Int): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 901ddc73..5302b320 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -3,34 +3,69 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert -import androidx.room.OnConflictStrategy.ABORT +import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe +import io.github.wulkanowy.data.db.entities.StudentIsAuthorized +import io.github.wulkanowy.data.db.entities.StudentIsEduOne +import io.github.wulkanowy.data.db.entities.StudentName +import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import javax.inject.Singleton @Singleton @Dao -interface StudentDao { +abstract class StudentDao { - @Insert(onConflict = ABORT) - fun insertAll(student: List): List + @Insert(onConflict = OnConflictStrategy.ABORT) + abstract suspend fun insertAll(student: List): List @Delete - fun delete(student: Student) + abstract suspend fun delete(student: Student) + + @Update(entity = Student::class) + abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized) + + @Update(entity = Student::class) + abstract suspend fun update(studentIsEduOne: StudentIsEduOne) + + @Update(entity = Student::class) + abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) + + @Update(entity = Student::class) + abstract suspend fun update(studentName: StudentName) @Query("SELECT * FROM Students WHERE is_current = 1") - fun loadCurrent(): Maybe + abstract suspend fun loadCurrent(): Student? @Query("SELECT * FROM Students WHERE id = :id") - fun loadById(id: Int): Maybe + abstract suspend fun loadById(id: Long): Student? @Query("SELECT * FROM Students") - fun loadAll(): Maybe> + abstract suspend fun loadAll(): List + + @Transaction + @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)") + abstract suspend fun loadStudentsWithSemesters(): Map> + + @Transaction + @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id") + abstract suspend fun loadStudentWithSemestersById(id: Long): Map> @Query("UPDATE Students SET is_current = 1 WHERE id = :id") - fun updateCurrent(id: Long) + abstract suspend fun updateCurrent(id: Long) @Query("UPDATE Students SET is_current = 0") - fun resetCurrent() + abstract suspend fun resetCurrent() + + @Query("DELETE FROM Students WHERE email = :email AND user_name = :userName") + abstract suspend fun deleteByEmailAndUserName(email: String, userName: String) + + @Transaction + open suspend fun switchCurrent(id: Long) { + resetCurrent() + updateCurrent(id) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt new file mode 100644 index 00000000..5ec86af1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.StudentInfo +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface StudentInfoDao : BaseDao { + + @Query("SELECT * FROM StudentInfo WHERE student_id = :studentId") + fun loadStudentInfo(studentId: Int): Flow +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt index 525a7129..4cd742b5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt @@ -3,11 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Subject -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow @Dao interface SubjectDao : BaseDao { @Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId") - fun loadAll(diaryId: Int, studentId: Int): Maybe> + fun loadAll(diaryId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt index 5ea237a8..6adac220 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Teacher -import io.reactivex.Maybe +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -11,5 +11,5 @@ import javax.inject.Singleton interface TeacherDao : BaseDao { @Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId") - fun loadAll(studentId: Int, classId: Int): Maybe> + fun loadAll(studentId: Int, classId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt new file mode 100644 index 00000000..914ce340 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate +import java.util.UUID +import javax.inject.Singleton + +@Dao +@Singleton +interface TimetableAdditionalDao : BaseDao { + + @Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") + fun loadAll( + diaryId: Int, + studentId: Int, + from: LocalDate, + end: LocalDate + ): Flow> + + @Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId") + suspend fun deleteAllByRepeatId(repeatId: UUID) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index 6b62cc82..40d97ea9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Timetable -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import javax.inject.Singleton @Singleton @@ -12,5 +12,8 @@ import javax.inject.Singleton interface TimetableDao : BaseDao { @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> + + @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") + suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableHeaderDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableHeaderDao.kt new file mode 100644 index 00000000..916d1901 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableHeaderDao.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.TimetableHeader +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate +import javax.inject.Singleton + +@Dao +@Singleton +interface TimetableHeaderDao : BaseDao { + + @Query("SELECT * FROM TimetableHeaders WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt new file mode 100644 index 00000000..a8604c5c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt @@ -0,0 +1,49 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@Entity(tableName = "AdminMessages") +data class AdminMessage( + + @PrimaryKey + val id: Int, + + val title: String, + + val content: String, + + @ColumnInfo(name = "version_name") + val versionMin: Int? = null, + + @ColumnInfo(name = "version_max") + val versionMax: Int? = null, + + @ColumnInfo(name = "target_register_host") + val targetRegisterHost: String? = null, + + @ColumnInfo(name = "target_flavor") + val targetFlavor: String? = null, + + @ColumnInfo(name = "destination_url") + val destinationUrl: String? = null, + + val priority: String, + + @SerialName("messageTypes") + @Serializable(with = SafeMessageTypeEnumListSerializer::class) + @ColumnInfo(name = "types", defaultValue = "[]") + val types: List = emptyList(), + + @ColumnInfo(name = "is_ok_visible", defaultValue = "0") + val isOkVisible: Boolean = false, + + @ColumnInfo(name = "is_x_visible", defaultValue = "0") + val isXVisible: Boolean = false +) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt index aa8da8db..b40dd52e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "Attendance") data class Attendance( @@ -47,4 +47,7 @@ data class Attendance( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt index d2e1f174..7d628eba 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.Month import java.io.Serializable +import java.time.Month @Entity(tableName = "AttendanceSummary") data class AttendanceSummary( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/CompletedLesson.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/CompletedLesson.kt index 775f3f55..e305d467 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/CompletedLesson.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/CompletedLesson.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "CompletedLesson") data class CompletedLesson( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt new file mode 100644 index 00000000..ba3958db --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.Instant + +@Entity(tableName = "Conferences") +data class Conference( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "diary_id") + val diaryId: Int, + + val title: String, + + val subject: String, + + val agenda: String, + + @ColumnInfo(name = "present_on_conference") + val presentOnConference: String, + + @ColumnInfo(name = "conference_id") + val conferenceId: Int, + + val date: Instant, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt index 9ae795e7..2292c3e6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "Exams") data class Exam( @@ -22,6 +22,7 @@ data class Exam( val subject: String, + @Deprecated("not available anymore") val group: String, val type: String, @@ -36,4 +37,7 @@ data class Exam( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt index 3f69c61b..a0f1c3a6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Grade.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "Grades") data class Grade( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt similarity index 61% rename from app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt rename to app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt index 8ad8b8b8..9aec9599 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt @@ -3,25 +3,25 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import java.io.Serializable -@Entity(tableName = "GradesStatistics") -data class GradeStatistics( - - @ColumnInfo(name = "student_id") - val studentId: Int, +@Entity(tableName = "GradesDescriptive") +data class GradeDescriptive( @ColumnInfo(name = "semester_id") val semesterId: Int, + @ColumnInfo(name = "student_id") + val studentId: Int, + val subject: String, - val grade: Int, + val description: String, +) : Serializable { - val amount: Int, - - @ColumnInfo(name = "is_semester") - val semester: Boolean -) { @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt new file mode 100644 index 00000000..db164afd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "GradePartialStatistics") +data class GradePartialStatistics( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "semester_id") + val semesterId: Int, + + val subject: String, + + @ColumnInfo(name = "class_average") + val classAverage: String, + + @ColumnInfo(name = "student_average") + val studentAverage: String, + + @ColumnInfo(name = "class_amounts") + val classAmounts: List, + + @ColumnInfo(name = "student_amounts") + val studentAmounts: List + +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt new file mode 100644 index 00000000..9e08b86b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "GradeSemesterStatistics") +data class GradeSemesterStatistics( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "semester_id") + val semesterId: Int, + + val subject: String, + + val amounts: List, + + @ColumnInfo(name = "student_grade") + val studentGrade: Int +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @Transient + var classAverage: String = "" + + @Transient + var studentAverage: String = "" +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt index 6e29112b..f8a357a3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import java.time.Instant @Entity(tableName = "GradesSummary") data class GradeSummary( @@ -32,8 +33,26 @@ data class GradeSummary( @ColumnInfo(name = "points_sum") val pointsSum: String, - val average: Double + @ColumnInfo(name = "points_sum_all_year") + val pointsSumAllYear: String?, + + val average: Double, + + @ColumnInfo(name = "average_all_year") + val averageAllYear: Double? = null, ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_predicted_grade_notified") + var isPredictedGradeNotified: Boolean = true + + @ColumnInfo(name = "is_final_grade_notified") + var isFinalGradeNotified: Boolean = true + + @ColumnInfo(name = "predicted_grade_last_change") + var predictedGradeLastChange: Instant = Instant.now() + + @ColumnInfo(name = "final_grade_last_change") + var finalGradeLastChange: Instant = Instant.now() } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt index cd7d153e..4538cf31 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "Homework") data class Homework( @@ -37,4 +37,10 @@ data class Homework( @ColumnInfo(name = "is_done") var isDone: Boolean = false + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true + + @ColumnInfo(name = "is_added_by_user") + var isAddedByUser: Boolean = false } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt index 5b9130f5..7c24c8f5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "LuckyNumbers") data class LuckyNumber ( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt new file mode 100644 index 00000000..e65e213d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.data.db.entities + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize + +@Parcelize +@Entity(tableName = "Mailboxes") +data class Mailbox( + + @PrimaryKey + val globalKey: String, + + val email: String, + val symbol: String, + val schoolId: String, + + val fullName: String, + val userName: String, + val studentName: String, + val schoolNameShort: String, + val type: MailboxType, +) : java.io.Serializable, Parcelable + +enum class MailboxType { + STUDENT, + PARENT, + GUARDIAN, + EMPLOYEE, + UNKNOWN, +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt index 05829841..d1356b33 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt @@ -3,48 +3,40 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDateTime import java.io.Serializable +import java.time.Instant @Entity(tableName = "Messages") data class Message( - @ColumnInfo(name = "student_id") - val studentId: Int, + @ColumnInfo(name = "email") + val email: String, - @ColumnInfo(name = "real_id") - val realId: Int, + @ColumnInfo(name = "message_global_key") + val messageGlobalKey: String, + + @ColumnInfo(name = "mailbox_key") + val mailboxKey: String, @ColumnInfo(name = "message_id") val messageId: Int, - @ColumnInfo(name = "sender_name") - val sender: String, - - @ColumnInfo(name = "sender_id") - val senderId: Int, - - @ColumnInfo(name = "recipient_name") - val recipient: String, + val correspondents: String, val subject: String, - var content: String, - - val date: LocalDateTime, + val date: Instant, @ColumnInfo(name = "folder_id") val folderId: Int, var unread: Boolean, - @ColumnInfo(name = "unread_by") - val unreadBy: Int, - @ColumnInfo(name = "read_by") - val readBy: Int, + val readBy: Int?, - val removed: Boolean, + @ColumnInfo(name = "unread_by") + val unreadBy: Int?, @ColumnInfo(name = "has_attachments") val hasAttachments: Boolean @@ -55,4 +47,8 @@ data class Message( @ColumnInfo(name = "is_notified") var isNotified: Boolean = true + + var content: String = "" + var sender: String? = null + var recipients: String? = null } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt index d1886e91..6f0c84ad 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt @@ -2,21 +2,16 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey import java.io.Serializable -@Entity(tableName = "MessageAttachments") +@Entity( + tableName = "MessageAttachments", + primaryKeys = ["message_global_key", "url", "filename"], +) data class MessageAttachment( - @PrimaryKey - @ColumnInfo(name = "real_id") - val realId: Int, - - @ColumnInfo(name = "message_id") - val messageId: Int, - - @ColumnInfo(name = "one_drive_id") - val oneDriveId: String, + @ColumnInfo(name = "message_global_key") + val messageGlobalKey: String, @ColumnInfo(name = "url") val url: String, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt index 2e7af0f4..fc890e76 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt @@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities import androidx.room.Embedded import androidx.room.Relation +import java.io.Serializable data class MessageWithAttachment( @Embedded val message: Message, - @Relation(parentColumn = "message_id", entityColumn = "message_id") - val attachments: List -) + @Relation(parentColumn = "message_global_key", entityColumn = "message_global_key") + val attachments: List, + + @Relation(parentColumn = "correspondents", entityColumn = "author") + val mutedMessageSender: MutedMessageSender?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt new file mode 100644 index 00000000..e3cd1ca7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.Embedded +import androidx.room.Relation + +data class MessageWithMutedAuthor( + @Embedded + val message: Message, + + @Relation(parentColumn = "correspondents", entityColumn = "author") + val mutedMessageSender: MutedMessageSender?, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt index f67ed599..44e90064 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt @@ -3,13 +3,13 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDateTime import java.io.Serializable +import java.time.Instant @Entity(tableName = "MobileDevices") data class MobileDevice( - @ColumnInfo(name = "student_id") + @ColumnInfo(name = "user_login_id") // todo: change column name val studentId: Int, @ColumnInfo(name = "device_id") @@ -17,7 +17,7 @@ data class MobileDevice( val name: String, - val date: LocalDateTime + val date: Instant, ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt new file mode 100644 index 00000000..f1770e64 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity(tableName = "MutedMessageSenders") +data class MutedMessageSender( + @ColumnInfo(name = "author") + val author: String, +) : Serializable { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Note.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Note.kt index 6f707a66..cfd54962 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Note.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Note.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate import java.io.Serializable +import java.time.LocalDate @Entity(tableName = "Notes") data class Note( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt new file mode 100644 index 00000000..c3267f24 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import io.github.wulkanowy.services.sync.notifications.NotificationType +import io.github.wulkanowy.ui.modules.Destination +import java.time.Instant + +@Entity(tableName = "Notifications") +data class Notification( + + @ColumnInfo(name = "student_id") + val studentId: Long, + + val title: String, + + val content: String, + + val type: NotificationType, + + @ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}") + val destination: Destination, + + val date: Instant, + + val data: String? = null +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt index 3021da72..d09742cd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt @@ -1,38 +1,22 @@ package io.github.wulkanowy.data.db.entities -import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable +@kotlinx.serialization.Serializable @Entity(tableName = "Recipients") data class Recipient( - - @ColumnInfo(name = "student_id") - val studentId: Int, - - @ColumnInfo(name = "real_id") - val realId: String, - - val name: String, - - @ColumnInfo(name = "real_name") - val realName: String, - - @ColumnInfo(name = "login_id") - val loginId: Int, - - @ColumnInfo(name = "unit_id") - val unitId: Int, - - val role: Int, - - val hash: String - + val mailboxGlobalKey: String, + val studentMailboxGlobalKey: String, + val fullName: String, + val userName: String, + val schoolShortName: String, + val type: MailboxType, ) : Serializable { @PrimaryKey(autoGenerate = true) var id: Long = 0 - override fun toString() = name + override fun toString() = userName } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/ReportingUnit.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/ReportingUnit.kt deleted file mode 100644 index 601d8aac..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/ReportingUnit.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.wulkanowy.data.db.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.io.Serializable - -@Entity(tableName = "ReportingUnits") -data class ReportingUnit( - - @ColumnInfo(name = "student_id") - val studentId: Int, - - @ColumnInfo(name = "real_id") - val realId: Int, - - @ColumnInfo(name = "short") - val shortName: String, - - @ColumnInfo(name = "sender_id") - val senderId: Int, - - @ColumnInfo(name = "sender_name") - val senderName: String, - - val roles: List - -) : Serializable { - - @PrimaryKey(autoGenerate = true) - var id: Long = 0 -} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt new file mode 100644 index 00000000..814a3c8d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.LocalDate + +@Entity(tableName = "SchoolAnnouncements") +data class SchoolAnnouncement( + + @ColumnInfo(name = "user_login_id") // todo: change column name + val studentId: Int, + + val date: LocalDate, + + val subject: String, + + val content: String, + + val author: String? = null, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt index 0641e0b6..187890c9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt @@ -4,9 +4,15 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate +import java.io.Serializable +import java.time.LocalDate -@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)]) +@Entity( + tableName = "Semesters", indices = [Index( + value = ["student_id", "diary_id", "kindergarten_diary_id", "semester_id"], + unique = true + )] +) data class Semester( @ColumnInfo(name = "student_id") @@ -15,6 +21,9 @@ data class Semester( @ColumnInfo(name = "diary_id") val diaryId: Int, + @ColumnInfo(name = "kindergarten_diary_id", defaultValue = "0") + val kindergartenDiaryId: Int, + @ColumnInfo(name = "diary_name") val diaryName: String, @@ -36,12 +45,11 @@ data class Semester( @ColumnInfo(name = "unit_id") val unitId: Int -) { +) : Serializable { @PrimaryKey(autoGenerate = true) var id: Long = 0 - @ColumnInfo(name = "is_current") var current: Boolean = false } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt index 905979f9..0300506a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt @@ -4,15 +4,24 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey -import org.threeten.bp.LocalDateTime import java.io.Serializable +import java.time.Instant -@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id", "class_id"], unique = true)]) +@Entity( + tableName = "Students", + indices = [Index( + value = ["email", "symbol", "student_id", "school_id", "class_id"], + unique = true + )] +) data class Student( @ColumnInfo(name = "scrapper_base_url") val scrapperBaseUrl: String, + @ColumnInfo(name = "scrapper_domain_suffix", defaultValue = "") + val scrapperDomainSuffix: String, + @ColumnInfo(name = "mobile_base_url") val mobileBaseUrl: String, @@ -40,16 +49,20 @@ data class Student( @ColumnInfo(name = "student_id") val studentId: Int, + @Deprecated("not available in VULCAN anymore") @ColumnInfo(name = "user_login_id") val userLoginId: Int, + @ColumnInfo(name = "user_name") + val userName: String, + @ColumnInfo(name = "student_name") val studentName: String, @ColumnInfo(name = "school_id") val schoolSymbol: String, - @ColumnInfo(name ="school_short") + @ColumnInfo(name = "school_short") val schoolShortName: String, @ColumnInfo(name = "school_name") @@ -65,9 +78,40 @@ data class Student( val isCurrent: Boolean, @ColumnInfo(name = "registration_date") - val registrationDate: LocalDateTime + val registrationDate: Instant, + + @ColumnInfo(name = "is_authorized", defaultValue = "0") + val isAuthorized: Boolean, + + @ColumnInfo(name = "is_edu_one", defaultValue = "NULL") + val isEduOne: Boolean?, + ) : Serializable { @PrimaryKey(autoGenerate = true) var id: Long = 0 + + var nick = "" + + @ColumnInfo(name = "avatar_color") + var avatarColor = 0L } + +@Entity +data class StudentIsAuthorized( + + @PrimaryKey + var id: Long, + + @ColumnInfo(name = "is_authorized", defaultValue = "NULL") + val isAuthorized: Boolean?, +) : Serializable + +@Entity +data class StudentIsEduOne( + @PrimaryKey + var id: Long, + + @ColumnInfo(name = "is_edu_one", defaultValue = "NULL") + val isEduOne: Boolean?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt new file mode 100644 index 00000000..7366e547 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt @@ -0,0 +1,85 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey +import io.github.wulkanowy.data.enums.Gender +import java.io.Serializable +import java.time.LocalDate + +@Entity(tableName = "StudentInfo") +data class StudentInfo( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "full_name") + val fullName: String, + + @ColumnInfo(name = "first_name") + val firstName: String, + + @ColumnInfo(name = "second_name") + val secondName: String, + + val surname: String, + + @ColumnInfo(name = "birth_date") + val birthDate: LocalDate, + + @ColumnInfo(name = "birth_place") + val birthPlace: String, + + val gender: Gender, + + @ColumnInfo(name = "has_polish_citizenship") + val hasPolishCitizenship: Boolean, + + @ColumnInfo(name = "family_name") + val familyName: String, + + @ColumnInfo(name = "parents_names") + val parentsNames: String, + + val address: String, + + @ColumnInfo(name = "registered_address") + val registeredAddress: String, + + @ColumnInfo(name = "correspondence_address") + val correspondenceAddress: String, + + @ColumnInfo(name = "phone_number") + val phoneNumber: String, + + @ColumnInfo(name = "cell_phone_number") + val cellPhoneNumber: String, + + val email: String, + + @Embedded(prefix = "first_guardian_") + val firstGuardian: StudentGuardian?, + + @Embedded(prefix = "second_guardian_") + val secondGuardian: StudentGuardian? + +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} + +data class StudentGuardian( + + @ColumnInfo(name = "full_name") + val fullName: String, + + val kinship: String, + + val address: String, + + val phones: String, + + val email: String +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt new file mode 100644 index 00000000..46f754b5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity +data class StudentName( + + @ColumnInfo(name = "student_name") + val studentName: String + +) : Serializable { + + @PrimaryKey + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNickAndAvatar.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNickAndAvatar.kt new file mode 100644 index 00000000..546059ee --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNickAndAvatar.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity +data class StudentNickAndAvatar( + + val nick: String, + + @ColumnInfo(name = "avatar_color") + var avatarColor: Long + +) : Serializable { + + @PrimaryKey + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt new file mode 100644 index 00000000..f9869d4e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.data.db.entities + +import java.io.Serializable + +data class StudentWithSemesters( + val student: Student, + val semesters: List +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt index cad3b7c6..d23d388f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime import java.io.Serializable +import java.time.Instant +import java.time.LocalDate @Entity(tableName = "Timetable") data class Timetable( @@ -18,9 +18,9 @@ data class Timetable( val number: Int, - val start: LocalDateTime, + val start: Instant, - val end: LocalDateTime, + val end: Instant, val date: LocalDate, @@ -50,4 +50,7 @@ data class Timetable( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt new file mode 100644 index 00000000..47802610 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt @@ -0,0 +1,37 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.Instant +import java.time.LocalDate +import java.util.* + +@Entity(tableName = "TimetableAdditional") +data class TimetableAdditional( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "diary_id") + val diaryId: Int, + + val start: Instant, + + val end: Instant, + + val date: LocalDate, + + val subject: String, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "repeat_id", defaultValue = "NULL") + var repeatId: UUID? = null + + @ColumnInfo(name = "is_added_by_user", defaultValue = "0") + var isAddedByUser: Boolean = false +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableHeader.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableHeader.kt new file mode 100644 index 00000000..7f21bf54 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableHeader.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.LocalDate + +@Entity(tableName = "TimetableHeaders") +data class TimetableHeader( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "diary_id") + val diaryId: Int, + + val date: LocalDate, + + val content: String, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt index c26a02d1..0e7e1409 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt @@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration10 : Migration(9, 10) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt index 6d129bca..342e2e2e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration11 : Migration(10, 11) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Grades_temp ( id INTEGER PRIMARY KEY NOT NULL, is_read INTEGER NOT NULL, @@ -26,9 +27,10 @@ class Migration11 : Migration(10, 11) { date INTEGER NOT NULL, teacher TEXT NOT NULL ) - """) - database.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades") - database.execSQL("DROP TABLE Grades") - database.execSQL("ALTER TABLE Grades_temp RENAME TO Grades") + """ + ) + db.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades") + db.execSQL("DROP TABLE Grades") + db.execSQL("ALTER TABLE Grades_temp RENAME TO Grades") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt index 1dc38e14..6cc72695 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt @@ -5,16 +5,17 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration12 : Migration(11, 12) { - override fun migrate(database: SupportSQLiteDatabase) { - createTempStudentsTable(database) - replaceStudentTable(database) - updateStudentsWithClassId(database, getStudentsIds(database)) - removeStudentsWithNoClassId(database) - ensureThereIsOnlyOneCurrentStudent(database) + override fun migrate(db: SupportSQLiteDatabase) { + createTempStudentsTable(db) + replaceStudentTable(db) + updateStudentsWithClassId(db, getStudentsIds(db)) + removeStudentsWithNoClassId(db) + ensureThereIsOnlyOneCurrentStudent(db) } - private fun createTempStudentsTable(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun createTempStudentsTable(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Students_tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, endpoint TEXT NOT NULL, @@ -30,40 +31,43 @@ class Migration12 : Migration(11, 12) { registration_date INTEGER NOT NULL, class_id INTEGER NOT NULL ) - """) - database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)") + """ + ) + db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)") } - private fun replaceStudentTable(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") - database.execSQL("INSERT INTO Students_tmp SELECT * FROM Students") - database.execSQL("DROP TABLE Students") - database.execSQL("ALTER TABLE Students_tmp RENAME TO Students") + private fun replaceStudentTable(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("INSERT INTO Students_tmp SELECT * FROM Students") + db.execSQL("DROP TABLE Students") + db.execSQL("ALTER TABLE Students_tmp RENAME TO Students") } private fun getStudentsIds(database: SupportSQLiteDatabase): List { val students = mutableListOf() - val studentsCursor = database.query("SELECT student_id FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0)) - } while (studentsCursor.moveToNext()) + database.query("SELECT student_id FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0)) + } while (it.moveToNext()) + } } + return students } - private fun updateStudentsWithClassId(database: SupportSQLiteDatabase, students: List) { + private fun updateStudentsWithClassId(db: SupportSQLiteDatabase, students: List) { students.forEach { - database.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it") + db.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it") } } - private fun removeStudentsWithNoClassId(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Students WHERE class_id = 0") + private fun removeStudentsWithNoClassId(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Students WHERE class_id = 0") } - private fun ensureThereIsOnlyOneCurrentStudent(database: SupportSQLiteDatabase) { - database.execSQL("UPDATE Students SET is_current = 0") - database.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)") + private fun ensureThereIsOnlyOneCurrentStudent(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE Students SET is_current = 0") + db.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt index 0cf8cd9b..c5030232 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt @@ -5,60 +5,70 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration13 : Migration(12, 13) { - override fun migrate(database: SupportSQLiteDatabase) { - addClassNameToStudents(database, getStudentsIds(database)) - updateSemestersTable(database) - markAtLeastAndOnlyOneSemesterAtCurrent(database, getStudentsAndClassIds(database)) - clearMessagesTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + addClassNameToStudents(db, getStudentsIds(db)) + updateSemestersTable(db) + markAtLeastAndOnlyOneSemesterAtCurrent(db, getStudentsAndClassIds(db)) + clearMessagesTable(db) } - private fun addClassNameToStudents(database: SupportSQLiteDatabase, students: List>) { - database.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL") + private fun addClassNameToStudents( + db: SupportSQLiteDatabase, + students: List> + ) { + db.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL") students.forEach { (id, name) -> val schoolName = name.substringAfter(" - ") val className = name.substringBefore(" - ", "").replace("Klasa ", "") - database.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'") - database.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'") + db.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'") + db.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'") } } - private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList> { + private fun getStudentsIds(db: SupportSQLiteDatabase): MutableList> { val students = mutableListOf>() - val studentsCursor = database.query("SELECT id, school_name FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0) to studentsCursor.getString(1)) - } while (studentsCursor.moveToNext()) + db.query("SELECT id, school_name FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0) to it.getString(1)) + } while (it.moveToNext()) + } } + return students } - private fun updateSemestersTable(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL") + private fun updateSemestersTable(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL") } - private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List> { + private fun getStudentsAndClassIds(db: SupportSQLiteDatabase): List> { val students = mutableListOf>() - val studentsCursor = database.query("SELECT student_id, class_id FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1)) - } while (studentsCursor.moveToNext()) + db.query("SELECT student_id, class_id FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0) to it.getInt(1)) + } while (it.moveToNext()) + } } + return students } - private fun markAtLeastAndOnlyOneSemesterAtCurrent(database: SupportSQLiteDatabase, students: List>) { + private fun markAtLeastAndOnlyOneSemesterAtCurrent( + db: SupportSQLiteDatabase, + students: List> + ) { students.forEach { (studentId, classId) -> - database.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'") - database.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)") + db.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'") + db.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)") } } - private fun clearMessagesTable(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Messages") + private fun clearMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Messages") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt index 4dac0d30..793b4a9d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration14 : Migration(13, 14) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS GradesSummary") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS GradesSummary") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesSummary ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, semester_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt index 5be49a95..5ff44e9c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration15 : Migration(14, 15) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS MobileDevices ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -14,6 +15,7 @@ class Migration15 : Migration(14, 15) { name TEXT NOT NULL, date INTEGER NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt index 7f40c0f8..8a8f5b8f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration16 : Migration(15, 16) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Teachers ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -15,6 +16,7 @@ class Migration16 : Migration(15, 16) { name TEXT NOT NULL, short_name TEXT NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt index e2a2574d..cf3318ad 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt @@ -5,13 +5,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration17 : Migration(16, 17) { - override fun migrate(database: SupportSQLiteDatabase) { - createGradesPointsStatisticsTable(database) - truncateSemestersTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + createGradesPointsStatisticsTable(db) + truncateSemestersTable(db) } - private fun createGradesPointsStatisticsTable(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun createGradesPointsStatisticsTable(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesPointsStatistics( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -20,10 +21,11 @@ class Migration17 : Migration(16, 17) { others REAL NOT NULL, student REAL NOT NULL ) - """) + """ + ) } - private fun truncateSemestersTable(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Semesters") + private fun truncateSemestersTable(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Semesters") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt index 6c5e56c6..713f8e72 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration18 : Migration(17, 18) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS School ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt index d38f1245..021cdbb3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt @@ -6,16 +6,17 @@ import io.github.wulkanowy.data.db.SharedPrefProvider class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migration(18, 19) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateMessages(database) - migrateGrades(database) - migrateStudents(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateMessages(db) + migrateGrades(db) + migrateStudents(db) migrateSharedPreferences() } - private fun migrateMessages(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Messages") - database.execSQL(""" + private fun migrateMessages(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, is_notified INTEGER NOT NULL, @@ -34,12 +35,14 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio read_by INTEGER NOT NULL, removed INTEGER NOT NULL ) - """) + """ + ) } - private fun migrateGrades(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Grades") - database.execSQL(""" + private fun migrateGrades(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Grades") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Grades ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, is_read INTEGER NOT NULL, @@ -59,11 +62,13 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio date INTEGER NOT NULL, teacher TEXT NOT NULL ) - """) + """ + ) } - private fun migrateStudents(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun migrateStudents(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Students_tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, scrapper_base_url TEXT NOT NULL, @@ -86,26 +91,29 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio is_current INTEGER NOT NULL, registration_date INTEGER NOT NULL ) - """) + """ + ) - database.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;") - database.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;") + db.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;") + db.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;") - database.execSQL(""" + db.execSQL( + """ INSERT INTO Students_tmp( id, scrapper_base_url, mobile_base_url, is_parent, login_type, login_mode, certificate_key, private_key, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date) SELECT id, endpoint, apiBaseUrl, is_parent, loginType, "SCRAPPER", certificateKey, privateKey, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date FROM Students - """) - database.execSQL("DROP TABLE Students") - database.execSQL("ALTER TABLE Students_tmp RENAME TO Students") - database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)") + """ + ) + db.execSQL("DROP TABLE Students") + db.execSQL("ALTER TABLE Students_tmp RENAME TO Students") + db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)") } private fun migrateSharedPreferences() { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt index c5a30991..be867509 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt @@ -5,14 +5,16 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration2 : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS LuckyNumbers ( id INTEGER PRIMARY KEY NOT NULL, is_notified INTEGER NOT NULL, student_id INTEGER NOT NULL, date INTEGER NOT NULL, lucky_number INTEGER NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt index 2fcfc183..7ad43230 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt @@ -5,14 +5,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration20 : Migration(19, 20) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateTimetable(database) - truncateSubjects(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateTimetable(db) + truncateSubjects(db) } - private fun migrateTimetable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Timetable") - database.execSQL(""" + private fun migrateTimetable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Timetable") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS `Timetable` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, @@ -33,10 +34,11 @@ class Migration20 : Migration(19, 20) { `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL ) - """) + """ + ) } - private fun truncateSubjects(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Subjects") + private fun truncateSubjects(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt index bc0ff900..60e044cd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt @@ -5,11 +5,11 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration21 : Migration(20, 21) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL") - database.execSQL("DELETE FROM Semesters") + db.execSQL("DELETE FROM Semesters") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt index cf50a6c3..ef525a49 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt @@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration22 : Migration(21, 22) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt index 22de94c3..3650307a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt @@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration23 : Migration(22, 23) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''") - database.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''") + db.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt index 604ed487..a3cd9819 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration24 : Migration(23, 24) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS MessageAttachments ( real_id INTEGER NOT NULL, message_id INTEGER NOT NULL, @@ -16,6 +17,7 @@ class Migration24 : Migration(23, 24) { filename TEXT NOT NULL, PRIMARY KEY(real_id) ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt index 4749bac7..cb395d7e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration25 : Migration(24, 25) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt new file mode 100644 index 00000000..94746b45 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration26 : Migration(25, 26) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt new file mode 100644 index 00000000..a7ba763d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration27 : Migration(26, 27) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN user_name TEXT NOT NULL DEFAULT \"\"") + + val students = getStudentsIdsAndNames(db) + val units = getReportingUnits(db) + + students.forEach { (id, userLoginId, studentName) -> + val userNameFromUnits = + units.singleOrNull { (senderId, _) -> senderId == userLoginId }?.second + val normalizedStudentName = studentName.split(" ").asReversed().joinToString(" ") + + val userName = userNameFromUnits ?: normalizedStudentName + db.execSQL("UPDATE Students SET user_name = '$userName' WHERE id = '$id'") + } + } + + private fun getStudentsIdsAndNames(db: SupportSQLiteDatabase): MutableList> { + val students = mutableListOf>() + db.query("SELECT id, user_login_id, student_name FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2))) + } while (it.moveToNext()) + } + } + + return students + } + + private fun getReportingUnits(db: SupportSQLiteDatabase): MutableList> { + val units = mutableListOf>() + db.query("SELECT sender_id, sender_name FROM ReportingUnits").use { + if (it.moveToFirst()) { + do { + units.add(it.getInt(0) to it.getString(1)) + } while (it.moveToNext()) + } + } + + + return units + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt new file mode 100644 index 00000000..e8a5a4a8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt @@ -0,0 +1,24 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration28 : Migration(27, 28) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS Conferences ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + diary_id INTEGER NOT NULL, + title TEXT NOT NULL, + subject TEXT NOT NULL, + agenda TEXT NOT NULL, + present_on_conference TEXT NOT NULL, + conference_id INTEGER NOT NULL, + date INTEGER NOT NULL + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt new file mode 100644 index 00000000..dac303d2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration29 : Migration(28, 29) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS GradesStatistics") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS GradeSemesterStatistics ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + semester_id INTEGER NOT NULL, + subject TEXT NOT NULL, + amounts TEXT NOT NULL, + student_grade INTEGER NOT NULL + ) + """ + ) + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS GradePartialStatistics ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + semester_id INTEGER NOT NULL, + subject TEXT NOT NULL, + class_average TEXT NOT NULL, + student_average TEXT NOT NULL, + class_amounts TEXT NOT NULL, + student_amounts TEXT NOT NULL + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt index d9699c0f..44d42164 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration3 : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS CompletedLesson ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt new file mode 100644 index 00000000..3fea8ec0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration30 : Migration(29, 30) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE TimetableAdditional ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + diary_id INTEGER NOT NULL, + start INTEGER NOT NULL, + `end` INTEGER NOT NULL, + date INTEGER NOT NULL, + subject TEXT NOT NULL + ) + """ + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt new file mode 100644 index 00000000..28fb1056 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration31 : Migration(30, 31) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """CREATE TABLE IF NOT EXISTS StudentInfo ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + full_name TEXT NOT NULL, + first_name TEXT NOT NULL, + second_name TEXT NOT NULL, + surname TEXT NOT NULL, + birth_date INTEGER NOT NULL, + birth_place TEXT NOT NULL, + gender TEXT NOT NULL, + has_polish_citizenship INTEGER NOT NULL, + family_name TEXT NOT NULL, + parents_names TEXT NOT NULL, + address TEXT NOT NULL, + registered_address TEXT NOT NULL, + correspondence_address TEXT NOT NULL, + phone_number TEXT NOT NULL, + cell_phone_number TEXT NOT NULL, + email TEXT NOT NULL, + first_guardian_full_name TEXT NOT NULL, + first_guardian_kinship TEXT NOT NULL, + first_guardian_address TEXT NOT NULL, + first_guardian_phones TEXT NOT NULL, + first_guardian_email TEXT NOT NULL, + second_guardian_full_name TEXT NOT NULL, + second_guardian_kinship TEXT NOT NULL, + second_guardian_address TEXT NOT NULL, + second_guardian_phones TEXT NOT NULL, + second_guardian_email TEXT NOT NULL) + """ + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt new file mode 100644 index 00000000..34787393 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration32 : Migration(31, 32) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"") + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt new file mode 100644 index 00000000..9778d279 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt @@ -0,0 +1,45 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration33 : Migration(32, 33) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS StudentInfo") + + db.execSQL( + """CREATE TABLE IF NOT EXISTS StudentInfo ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + full_name TEXT NOT NULL, + first_name TEXT NOT NULL, + second_name TEXT NOT NULL, + surname TEXT NOT NULL, + birth_date INTEGER NOT NULL, + birth_place TEXT NOT NULL, + gender TEXT NOT NULL, + has_polish_citizenship INTEGER NOT NULL, + family_name TEXT NOT NULL, + parents_names TEXT NOT NULL, + address TEXT NOT NULL, + registered_address TEXT NOT NULL, + correspondence_address TEXT NOT NULL, + phone_number TEXT NOT NULL, + cell_phone_number TEXT NOT NULL, + email TEXT NOT NULL, + first_guardian_full_name TEXT, + first_guardian_kinship TEXT, + first_guardian_address TEXT, + first_guardian_phones TEXT, + first_guardian_email TEXT, + second_guardian_full_name TEXT, + second_guardian_kinship TEXT, + second_guardian_address TEXT, + second_guardian_phones TEXT, + second_guardian_email TEXT) + """ + ) + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt new file mode 100644 index 00000000..e9eec58c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration34 : Migration(33, 34) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM ReportingUnits") + db.execSQL("DELETE FROM Recipients") + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt new file mode 100644 index 00000000..b238ce8b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.core.database.getLongOrNull +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import io.github.wulkanowy.utils.AppInfo + +class Migration35(private val appInfo: AppInfo) : Migration(34, 35) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0") + + db.query("SELECT * FROM Students").use { + while (it.moveToNext()) { + val studentId = it.getLongOrNull(0) + db.execSQL( + """ + UPDATE Students + SET avatar_color = ${appInfo.defaultColorsForAvatar.random()} + WHERE id = $studentId + """ + ) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt new file mode 100644 index 00000000..62ce346c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration36 : Migration(35, 36) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt new file mode 100644 index 00000000..9ab35514 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration37 : Migration(36, 37) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS TimetableHeaders ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + diary_id INTEGER NOT NULL, + date INTEGER NOT NULL, + content TEXT NOT NULL + ) + """ + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt new file mode 100644 index 00000000..bb9b32bf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration38 : Migration(37, 38) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `student_id` INTEGER NOT NULL, + `date` INTEGER NOT NULL, + `subject` TEXT NOT NULL, + `content` TEXT NOT NULL + ) + """ + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt new file mode 100644 index 00000000..2e5315bf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration39 : Migration(38, 39) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt index 0ae89bdd..b6089aa6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration4 : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY NOT NULL, is_notified INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt new file mode 100644 index 00000000..8e38b0c8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration40 : Migration(39, 40) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Notifications` ( + `student_id` INTEGER NOT NULL, + `title` TEXT NOT NULL, + `content` TEXT NOT NULL, + `type` TEXT NOT NULL, + `date` INTEGER NOT NULL, + `data` TEXT, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL + ) + """ + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt new file mode 100644 index 00000000..bfc28334 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.enums.GradeExpandMode + +class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) { + + override fun migrate(db: SupportSQLiteDatabase) { + migrateSharedPreferences() + db.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0") + } + + private fun migrateSharedPreferences() { + if (sharedPrefProvider.getBoolean("pref_key_expand_grade", false)) { + sharedPrefProvider.putString("pref_key_expand_grade_mode", GradeExpandMode.ALWAYS_EXPANDED.value) + } + sharedPrefProvider.delete("pref_key_expand_grade") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt new file mode 100644 index 00000000..14356e27 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt @@ -0,0 +1,24 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration42 : Migration(41, 42) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """CREATE TABLE IF NOT EXISTS `AdminMessages` ( + `id` INTEGER NOT NULL, + `title` TEXT NOT NULL, + `content` TEXT NOT NULL, + `version_name` INTEGER, + `version_max` INTEGER, + `target_register_host` TEXT, + `target_flavor` TEXT, + `destination_url` TEXT, + `priority` TEXT NOT NULL, + `type` TEXT NOT NULL, + PRIMARY KEY(`id`))""" + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt new file mode 100644 index 00000000..ef810816 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration43 : Migration(42, 43) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt new file mode 100644 index 00000000..0a4e5f96 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration44 : Migration(43, 44) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt new file mode 100644 index 00000000..0bacbaa0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt @@ -0,0 +1,102 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset + +class Migration46 : Migration(45, 46) { + + override fun migrate(db: SupportSQLiteDatabase) { + migrateConferences(db) + migrateMessages(db) + migrateMobileDevices(db) + migrateNotifications(db) + migrateTimetable(db) + migrateTimetableAdditional(db) + } + + private fun migrateConferences(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Conferences").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + db.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateMessages(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Messages").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + db.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateMobileDevices(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM MobileDevices").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + db.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateNotifications(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Notifications").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + db.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateTimetable(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Timetable").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) + val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end")) + val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() + val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() + + db.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + } + } + } + + private fun migrateTimetableAdditional(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM TimetableAdditional").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) + val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end")) + val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() + val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() + + db.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + } + } + } + + private fun Long.timestampLocalToUTC(): Long = Instant.ofEpochMilli(this) + .atZone(ZoneOffset.UTC) + .withZoneSameLocal(ZoneId.of("Europe/Warsaw")) + .withZoneSameInstant(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli() +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt new file mode 100644 index 00000000..97766c01 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration49 : Migration(48, 49) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements") + + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` ( + `user_login_id` INTEGER NOT NULL, + `date` INTEGER NOT NULL, + `subject` TEXT NOT NULL, + `content` TEXT NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `is_notified` INTEGER NOT NULL) + """.trimIndent() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt index fe0dec48..a5b4e8e1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt @@ -2,16 +2,21 @@ package io.github.wulkanowy.data.db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import org.threeten.bp.LocalDateTime.now -import org.threeten.bp.ZoneOffset +import java.time.LocalDateTime.now +import java.time.ZoneOffset class Migration5 : Migration(4, 5) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL") - database.execSQL("UPDATE Students SET registration_date = '${now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()}'") - database.execSQL("DROP TABLE IF EXISTS Notes") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL") + db.execSQL( + "UPDATE Students SET registration_date = '${ + now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() + }'" + ) + db.execSQL("DROP TABLE IF EXISTS Notes") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Notes ( id INTEGER PRIMARY KEY NOT NULL, is_read INTEGER NOT NULL, @@ -21,6 +26,7 @@ class Migration5 : Migration(4, 5) { teacher TEXT NOT NULL, category TEXT NOT NULL, content TEXT NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt new file mode 100644 index 00000000..577998ca --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration50 : Migration(49, 50) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS MobileDevices") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `MobileDevices` ( + `user_login_id` INTEGER NOT NULL, + `device_id` INTEGER NOT NULL, + `name` TEXT NOT NULL, + `date` INTEGER NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL) + """.trimIndent() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt new file mode 100644 index 00000000..7023049f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt @@ -0,0 +1,88 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration51 : Migration(50, 51) { + + override fun migrate(db: SupportSQLiteDatabase) { + createMailboxTable(db) + recreateMessagesTable(db) + recreateMessageAttachmentsTable(db) + recreateRecipientsTable(db) + deleteReportingUnitTable(db) + } + + private fun createMailboxTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Mailboxes") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Mailboxes` ( + `globalKey` TEXT NOT NULL, + `fullName` TEXT NOT NULL, + `userName` TEXT NOT NULL, + `userLoginId` INTEGER NOT NULL, + `studentName` TEXT NOT NULL, + `schoolNameShort` TEXT NOT NULL, + `type` TEXT NOT NULL, + PRIMARY KEY(`globalKey`) + )""".trimIndent() + ) + } + + private fun recreateMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Messages` ( + `message_global_key` TEXT NOT NULL, + `mailbox_key` TEXT NOT NULL, + `message_id` INTEGER NOT NULL, + `correspondents` TEXT NOT NULL, + `subject` TEXT NOT NULL, + `date` INTEGER NOT NULL, + `folder_id` INTEGER NOT NULL, + `unread` INTEGER NOT NULL, + `has_attachments` INTEGER NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `is_notified` INTEGER NOT NULL, + `content` TEXT NOT NULL, + `sender` TEXT, `recipients` TEXT + )""".trimIndent() + ) + } + + private fun recreateMessageAttachmentsTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS MessageAttachments") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `MessageAttachments` ( + `real_id` INTEGER NOT NULL, + `message_global_key` TEXT NOT NULL, + `url` TEXT NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`real_id`) + )""".trimIndent() + ) + } + + private fun recreateRecipientsTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Recipients") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Recipients` ( + `mailboxGlobalKey` TEXT NOT NULL, + `studentMailboxGlobalKey` TEXT NOT NULL, + `fullName` TEXT NOT NULL, + `userName` TEXT NOT NULL, + `schoolShortName` TEXT NOT NULL, + `type` TEXT NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL + )""".trimIndent() + ) + } + + private fun deleteReportingUnitTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS ReportingUnits") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt new file mode 100644 index 00000000..dd9e68c9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt @@ -0,0 +1,57 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration53 : Migration(52, 53) { + + override fun migrate(db: SupportSQLiteDatabase) { + createMailboxTable(db) + recreateMessagesTable(db) + } + + private fun createMailboxTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Mailboxes") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Mailboxes` ( + `globalKey` TEXT NOT NULL, + `email` TEXT NOT NULL, + `symbol` TEXT NOT NULL, + `schoolId` TEXT NOT NULL, + `fullName` TEXT NOT NULL, + `userName` TEXT NOT NULL, + `studentName` TEXT NOT NULL, + `schoolNameShort` TEXT NOT NULL, + `type` TEXT NOT NULL, + PRIMARY KEY(`globalKey`) + )""".trimIndent() + ) + } + + private fun recreateMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Messages` ( + `email` TEXT NOT NULL, + `message_global_key` TEXT NOT NULL, + `mailbox_key` TEXT NOT NULL, + `message_id` INTEGER NOT NULL, + `correspondents` TEXT NOT NULL, + `subject` TEXT NOT NULL, + `date` INTEGER NOT NULL, + `folder_id` INTEGER NOT NULL, + `unread` INTEGER NOT NULL, + `read_by` INTEGER, + `unread_by` INTEGER, + `has_attachments` INTEGER NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `is_notified` INTEGER NOT NULL, + `content` TEXT NOT NULL, + `sender` TEXT, + `recipients` TEXT + )""".trimIndent() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt new file mode 100644 index 00000000..60bd21f0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration54 : Migration(53, 54) { + + override fun migrate(db: SupportSQLiteDatabase) { + migrateResman(db) + removeTomaszowMazowieckiStudents(db) + } + + private fun migrateResman(db: SupportSQLiteDatabase) { + db.execSQL( + """ + UPDATE Students SET + scrapper_base_url = 'https://vulcan.net.pl', + login_type = 'ADFSLightScoped', + symbol = 'rzeszowprojekt' + WHERE scrapper_base_url = 'https://resman.pl' + """.trimIndent() + ) + } + + private fun removeTomaszowMazowieckiStudents(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt new file mode 100644 index 00000000..424be171 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.DeleteColumn +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase + +@DeleteColumn( + tableName = "MessageAttachments", + columnName = "real_id", +) +class Migration55 : AutoMigrationSpec { + + override fun onPostMigrate(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Messages") + db.execSQL("DELETE FROM MessageAttachments") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt new file mode 100644 index 00000000..2fc8718f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt @@ -0,0 +1,10 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.DeleteColumn +import androidx.room.migration.AutoMigrationSpec + +@DeleteColumn( + tableName = "AdminMessages", + columnName = "type", +) +class Migration57 : AutoMigrationSpec diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt new file mode 100644 index 00000000..c440d58d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt @@ -0,0 +1,10 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.DeleteColumn +import androidx.room.migration.AutoMigrationSpec + +@DeleteColumn( + tableName = "AdminMessages", + columnName = "is_dismissible", +) +class Migration58 : AutoMigrationSpec diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt index fa943618..06cd5f0f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration6 : Migration(5, 6) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS ReportingUnits ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -15,9 +16,11 @@ class Migration6 : Migration(5, 6) { sender_id INTEGER NOT NULL, sender_name TEXT NOT NULL, roles TEXT NOT NULL) - """) + """ + ) - database.execSQL(""" + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Recipients ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -28,10 +31,11 @@ class Migration6 : Migration(5, 6) { unit_id INTEGER NOT NULL, role INTEGER NOT NULL, hash TEXT NOT NULL) - """) + """ + ) - database.execSQL("DELETE FROM Semesters WHERE 1") - database.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("DELETE FROM Semesters WHERE 1") + db.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt new file mode 100644 index 00000000..f88d31fc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration63 : AutoMigrationSpec { + + override fun onPostMigrate(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt index 120716c8..83a822f2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration7 : Migration(6, 7) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesStatistics ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -15,6 +16,7 @@ class Migration7 : Migration(6, 7) { grade INTEGER NOT NULL, amount INTEGER NOT NULL, is_semester INTEGER NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt index 7009ee12..992e8c68 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt @@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration8 : Migration(7, 8) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL") - database.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL") - database.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL") + db.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL") + db.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt index d79a5706..b83c34c4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration9 : Migration(8, 9) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/AppTheme.kt b/app/src/main/java/io/github/wulkanowy/data/enums/AppTheme.kt new file mode 100644 index 00000000..acff9d2e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/AppTheme.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.enums + +enum class AppTheme(val value: String) { + SYSTEM("system"), + LIGHT("light"), + DARK("dark"), + BLACK("black"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: LIGHT + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt new file mode 100644 index 00000000..77dd5fc4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.enums + +enum class AttendanceCalculatorSortingMode(private val value: String) { + ALPHABETIC("alphabetic"), + ATTENDANCE("attendance_percentage"), + LESSON_BALANCE("lesson_balance"); + + companion object { + fun getByValue(value: String) = + AttendanceCalculatorSortingMode.values() + .find { it.value == value } ?: ALPHABETIC + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt b/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt new file mode 100644 index 00000000..df93dcbe --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt @@ -0,0 +1,3 @@ +package io.github.wulkanowy.data.enums + +enum class Gender { MALE, FEMALE } diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/GradeColorTheme.kt b/app/src/main/java/io/github/wulkanowy/data/enums/GradeColorTheme.kt new file mode 100644 index 00000000..48b81670 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/GradeColorTheme.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.enums + +import java.io.Serializable + +enum class GradeColorTheme(val value: String) : Serializable { + VULCAN("vulcan"), + MATERIAL("material"), + GRADE_COLOR("grade_color"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: VULCAN + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/GradeExpandMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/GradeExpandMode.kt new file mode 100644 index 00000000..f99fe7a8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/GradeExpandMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class GradeExpandMode(val value: String) { + ONE("one"), + UNLIMITED("any"), + ALWAYS_EXPANDED("always"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: ONE + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/GradeSortingMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/GradeSortingMode.kt new file mode 100644 index 00000000..e2872728 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/GradeSortingMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class GradeSortingMode(val value: String) { + ALPHABETIC("alphabetic"), + DATE("date"), + AVERAGE("average"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: ALPHABETIC + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt new file mode 100644 index 00000000..7cb4202a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.enums + +enum class MessageFolder(val id: Int = 1) { + RECEIVED(1), + SENT(2), + TRASHED(3), + ; + + companion object { + fun byId(id: Int) = entries.first { it.id == id } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt new file mode 100644 index 00000000..ecd8d916 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class MessageType { + GENERAL_MESSAGE, + DASHBOARD_MESSAGE, + LOGIN_MESSAGE, + LOGIN_STUDENT_SELECT_MESSAGE, + LOGIN_SYMBOL_MESSAGE, + PASS_RESET_MESSAGE, + ERROR_OVERRIDE, +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt b/app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt similarity index 60% rename from app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt rename to app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt index 50d6b8ed..99878152 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt +++ b/app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.attendance +package io.github.wulkanowy.data.enums enum class SentExcuseStatus(val id: Int = 0) { WAITING, diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt new file mode 100644 index 00000000..3e7cdef5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class ShowAdditionalLessonsMode(val value: String) { + NONE("none"), + INLINE("inline"), + BELOW("below"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt new file mode 100644 index 00000000..c8310c02 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class TimetableGapsMode(val value: String) { + NO_GAPS("no_gaps"), + BETWEEN_LESSONS("between"), + BETWEEN_AND_BEFORE_LESSONS("before_and_between"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: BETWEEN_LESSONS + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/TimetableMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableMode.kt new file mode 100644 index 00000000..b2eeec38 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class TimetableMode(val value: String) { + WHOLE_PLAN("yes"), + ONLY_CURRENT_GROUP("no"), + SMALL_OTHER_GROUP("small"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: ONLY_CURRENT_GROUP + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt new file mode 100644 index 00000000..c0ed0c8c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt @@ -0,0 +1,48 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance +import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary + +fun List.mapToEntities(semester: Semester, lessons: List) = map { + Attendance( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + timeId = it.timeId, + number = it.number, + subject = it.subject.ifBlank { + lessons.find { lesson -> + lesson.date == it.date && lesson.number == it.number + }?.subject.orEmpty() + }, + name = it.name, + presence = it.presence, + absence = it.absence, + exemption = it.exemption, + lateness = it.lateness, + excused = it.excused, + deleted = it.deleted, + excusable = it.excusable, + excuseStatus = it.excuseStatus?.name + ) +} + +fun List.mapToEntities(semester: Semester, subjectId: Int) = map { + AttendanceSummary( + studentId = semester.studentId, + diaryId = semester.diaryId, + subjectId = subjectId, + month = it.month, + presence = it.presence, + absence = it.absence, + absenceExcused = it.absenceExcused, + absenceForSchoolReasons = it.absenceForSchoolReasons, + lateness = it.lateness, + latenessExcused = it.latenessExcused, + exemption = it.exemption + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt new file mode 100644 index 00000000..c42126eb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.CompletedLesson as SdkCompletedLesson + +fun List.mapToEntities(semester: Semester) = map { + CompletedLesson( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + number = it.number, + subject = it.subject, + topic = it.topic, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + substitution = it.substitution, + absence = it.absence, + resources = it.resources + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt new file mode 100644 index 00000000..add6439d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Conference as SdkConference + +fun List.mapToEntities(semester: Semester) = map { + Conference( + studentId = semester.studentId, + diaryId = semester.diaryId, + agenda = it.agenda, + conferenceId = it.id, + date = it.date.toInstant(), + presentOnConference = it.presentOnConference, + subject = it.topic, + title = it.place, + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt new file mode 100644 index 00000000..1a84a6a5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation +import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement + +@JvmName("mapDirectorInformationToEntities") +fun List.mapToEntities(student: Student) = map { + SchoolAnnouncement( + studentId = student.studentId, + date = it.date, + subject = it.subject, + content = it.content, + author = null, + ) +} + +@JvmName("mapLastAnnouncementsToEntities") +fun List.mapToEntities(student: Student) = map { + SchoolAnnouncement( + studentId = student.studentId, + date = it.date, + subject = it.subject, + content = it.content, + author = it.author, + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt new file mode 100644 index 00000000..173dfebf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Exam as SdkExam + +fun List.mapToEntities(semester: Semester) = map { + Exam( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + entryDate = it.entryDate, + subject = it.subject, + group = "", + type = it.type, + description = it.description, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt new file mode 100644 index 00000000..57322a7a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade +import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive +import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary + +fun List.mapToEntities(semester: Semester) = map { + Grade( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = it.subject, + entry = it.entry, + value = it.value, + modifier = it.modifier, + comment = it.comment, + color = it.color, + gradeSymbol = it.symbol, + description = it.description, + weight = it.weight, + weightValue = it.weightValue, + date = it.date, + teacher = it.teacher + ) +} + +@JvmName("mapGradeSummaryToEntities") +fun List.mapToEntities(semester: Semester) = map { + GradeSummary( + semesterId = semester.semesterId, + studentId = semester.studentId, + position = 0, + subject = it.name, + predictedGrade = it.predicted, + finalGrade = it.final, + pointsSum = it.pointsSum, + pointsSumAllYear = it.pointsSumAllYear, + proposedPoints = it.proposedPoints, + finalPoints = it.finalPoints, + average = it.average, + averageAllYear = it.averageAllYear, + ) +} + +@JvmName("mapGradeDescriptiveToEntities") +fun List.mapToEntities(semester: Semester) = map { + GradeDescriptive( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + description = it.description + ) +} + + diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt new file mode 100644 index 00000000..b25802d2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt @@ -0,0 +1,79 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.sdk.pojo.GradePointsStatistics as SdkGradePointsStatistics +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject + +@JvmName("mapToEntitiesSubject") +fun List.mapToEntities(semester: Semester) = map { + GradePartialStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + classAverage = it.classAverage, + studentAverage = it.studentAverage, + classAmounts = it.classItems + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentAmounts = it.studentItems.map { item -> item.amount } + ) +} + +@JvmName("mapToEntitiesSemester") +fun List.mapToEntities(semester: Semester) = map { + GradeSemesterStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + amounts = it.items + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentGrade = it.items.singleOrNull { item -> item.isStudentHere }?.grade ?: 0 + ) +} + +@JvmName("mapToEntitiesPoints") +fun List.mapToEntities(semester: Semester) = map { + GradePointsStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + others = it.others, + student = it.student + ) +} + +fun List.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map { + GradeStatisticsItem( + type = GradeStatisticsItem.DataType.PARTIAL, + average = it.classAverage, + partial = it, + points = null, + semester = null + ) +} + +fun List.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map { + GradeStatisticsItem( + type = GradeStatisticsItem.DataType.SEMESTER, + partial = null, + points = null, + average = "", + semester = it + ) +} + +fun List.mapPointsToStatisticsItems() = map { + GradeStatisticsItem( + type = GradeStatisticsItem.DataType.POINTS, + partial = null, + semester = null, + average = "", + points = it + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt new file mode 100644 index 00000000..880a26d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.sdk.pojo.Homework as SdkHomework +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.Semester + +fun List.mapToEntities(semester: Semester) = map { + Homework( + semesterId = semester.semesterId, + studentId = semester.studentId, + date = it.date, + entryDate = it.entryDate, + subject = it.subject, + content = it.content, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + attachments = it.attachments.map { attachment -> + attachment.url to attachment.name + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt new file mode 100644 index 00000000..78ebe1d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import java.time.LocalDate +import io.github.wulkanowy.sdk.pojo.LuckyNumber as SdkLuckyNumber + +fun SdkLuckyNumber.mapToEntity(student: Student) = LuckyNumber( + studentId = student.studentId, + date = LocalDate.now(), + luckyNumber = number +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt new file mode 100644 index 00000000..0cf54777 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.pojo.Mailbox as SdkMailbox + +fun List.mapToEntities(student: Student) = map { + Mailbox( + globalKey = it.globalKey, + fullName = it.fullName, + userName = it.userName, + studentName = it.studentName, + schoolNameShort = it.schoolNameShort, + type = MailboxType.valueOf(it.type.name), + email = student.email, + symbol = student.symbol, + schoolId = student.schoolSymbol, + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt new file mode 100644 index 00000000..a26d7665 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -0,0 +1,57 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.* +import io.github.wulkanowy.sdk.pojo.MailboxType +import timber.log.Timber +import io.github.wulkanowy.sdk.pojo.Message as SdkMessage +import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment +import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient + +fun List.mapToEntities( + student: Student, + mailbox: Mailbox?, + allMailboxes: List +): List = map { + Message( + messageGlobalKey = it.globalKey, + mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> + box.fullName == it.mailbox + }?.globalKey.let { mailboxKey -> + if (mailboxKey == null) { + Timber.e("Can't find ${it.mailbox} in $allMailboxes") + "unknown" + } else mailboxKey + }, + email = student.email, + messageId = it.id, + correspondents = it.correspondents, + subject = it.subject.trim(), + date = it.date.toInstant(), + folderId = it.folderId, + unread = it.unread, + unreadBy = it.unreadBy, + readBy = it.readBy, + hasAttachments = it.hasAttachments, + ).apply { + content = it.content.orEmpty() + } +} + +fun List.mapToEntities(messageGlobalKey: String) = map { + MessageAttachment( + messageGlobalKey = messageGlobalKey, + url = it.url, + filename = it.filename + ) +} + +fun List.mapFromEntities() = map { + SdkRecipient( + fullName = it.fullName, + userName = it.userName, + studentName = it.userName, + mailboxGlobalKey = it.mailboxGlobalKey, + schoolNameShort = it.schoolShortName, + type = MailboxType.valueOf(it.type.name), + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt new file mode 100644 index 00000000..3818f01a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.sdk.pojo.Device as SdkDevice +import io.github.wulkanowy.sdk.pojo.Token as SdkToken + +fun List.mapToEntities(student: Student) = map { + MobileDevice( + studentId = student.studentId, + date = it.createDate.toInstant(), + deviceId = it.id, + name = it.name + ) +} + +fun SdkToken.mapToMobileDeviceToken() = MobileDeviceToken( + token = token, + symbol = symbol, + pin = pin, + qr = qrCodeImage +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt new file mode 100644 index 00000000..70941799 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Note as SdkNote + +fun List.mapToEntities(semester: Semester) = map { + Note( + studentId = semester.studentId, + date = it.date, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + category = it.category, + categoryType = it.categoryType.id, + isPointsShow = it.showPoints, + points = it.points, + content = it.content + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt new file mode 100644 index 00000000..eb993a0f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient + +fun List.mapToEntities(studentMailboxGlobalKey: String) = map { + Recipient( + mailboxGlobalKey = it.mailboxGlobalKey, + fullName = it.fullName, + userName = it.userName, + studentMailboxGlobalKey = studentMailboxGlobalKey, + schoolShortName = it.schoolNameShort, + type = MailboxType.valueOf(it.type.name), + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt new file mode 100644 index 00000000..7e6a8166 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt @@ -0,0 +1,94 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.* +import java.time.Instant +import io.github.wulkanowy.sdk.pojo.RegisterStudent as SdkRegisterStudent +import io.github.wulkanowy.sdk.pojo.RegisterUser as SdkRegisterUser + +fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser( + email = email, + login = login, + password = password, + scrapperBaseUrl = scrapperBaseUrl, + loginMode = loginMode, + loginType = loginType, + symbols = symbols.map { registerSymbol -> + RegisterSymbol( + symbol = registerSymbol.symbol, + error = registerSymbol.error, + hebeBaseUrl = registerSymbol.hebeBaseUrl, + keyId = registerSymbol.keyId, + privatePem = registerSymbol.privatePem, + userName = registerSymbol.userName, + schools = registerSymbol.schools.map { + RegisterUnit( + userLoginId = it.userLoginId, + schoolId = it.schoolId, + schoolName = it.schoolName, + schoolShortName = it.schoolShortName, + parentIds = it.parentIds, + studentIds = it.studentIds, + employeeIds = it.employeeIds, + error = it.error, + students = it.subjects + .filterIsInstance() + .map { registerStudent -> + RegisterStudent( + studentId = registerStudent.studentId, + studentName = registerStudent.studentName, + studentSecondName = registerStudent.studentSecondName, + studentSurname = registerStudent.studentSurname, + className = registerStudent.className, + classId = registerStudent.classId, + isParent = registerStudent.isParent, + isAuthorized = registerStudent.isAuthorized, + isEduOne = registerStudent.isEduOne, + semesters = registerStudent.semesters + .mapToEntities(registerStudent.studentId), + ) + }, + ) + } + ) + }, +) + +fun RegisterStudent.mapToStudentWithSemesters( + user: RegisterUser, + scrapperDomainSuffix: String, + symbol: RegisterSymbol, + unit: RegisterUnit, + colors: List, +): StudentWithSemesters = StudentWithSemesters( + semesters = semesters, + student = Student( + email = user.login, // for compatibility + userName = symbol.userName, + userLoginId = unit.userLoginId, + isParent = isParent, + className = className, + classId = classId, + studentId = studentId, + symbol = symbol.symbol, + loginType = user.loginType?.name.orEmpty(), + schoolName = unit.schoolName, + schoolShortName = unit.schoolShortName, + schoolSymbol = unit.schoolId, + studentName = "$studentName $studentSurname", + loginMode = user.loginMode.name, + scrapperBaseUrl = user.scrapperBaseUrl.orEmpty(), + scrapperDomainSuffix = scrapperDomainSuffix, + mobileBaseUrl = symbol.hebeBaseUrl.orEmpty(), + certificateKey = symbol.keyId.orEmpty(), + privateKey = symbol.privatePem.orEmpty(), + password = user.password.orEmpty(), + isCurrent = false, + registrationDate = Instant.now(), + isAuthorized = this.isAuthorized, + isEduOne = this.isEduOne, + ).apply { + avatarColor = colors.random() + }, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt new file mode 100644 index 00000000..dc3a5a9e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.School as SdkSchool + +fun SdkSchool.mapToEntity(semester: Semester) = School( + studentId = semester.studentId, + classId = semester.classId, + name = name, + address = address, + contact = contact, + headmaster = headmaster, + pedagogue = pedagogue +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt new file mode 100644 index 00000000..67d68a1e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Semester as SdkSemester + +fun List.mapToEntities(studentId: Int) = map { + Semester( + studentId = studentId, + diaryId = it.diaryId, + kindergartenDiaryId = it.kindergartenDiaryId, + diaryName = it.diaryName, + schoolYear = it.schoolYear, + semesterId = it.semesterId, + semesterName = it.semesterNumber, + start = it.start, + end = it.end, + classId = it.classId, + unitId = it.unitId + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt new file mode 100644 index 00000000..9e853390 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.StudentGuardian +import io.github.wulkanowy.data.db.entities.StudentInfo +import io.github.wulkanowy.data.enums.Gender +import io.github.wulkanowy.sdk.pojo.StudentGuardian as SdkStudentGuardian +import io.github.wulkanowy.sdk.pojo.StudentInfo as SdkStudentInfo + +fun SdkStudentInfo.mapToEntity(semester: Semester) = StudentInfo( + studentId = semester.studentId, + fullName = fullName, + firstName = firstName, + secondName = secondName, + surname = surname, + birthDate = birthDate, + birthPlace = birthPlace, + gender = Gender.valueOf(gender.name), + hasPolishCitizenship = hasPolishCitizenship, + familyName = familyName, + parentsNames = parentsNames, + address = address, + registeredAddress = registeredAddress, + correspondenceAddress = correspondenceAddress, + phoneNumber = phoneNumber, + cellPhoneNumber = phoneNumber, + email = email, + firstGuardian = guardianFirst?.mapToEntity(), + secondGuardian = guardianSecond?.mapToEntity() +) + +fun SdkStudentGuardian.mapToEntity() = StudentGuardian( + fullName = fullName, + kinship = kinship, + address = address, + phones = phones, + email = email +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt new file mode 100644 index 00000000..4dc95aaa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.sdk.pojo.Subject as SdkSubject + +fun List.mapToEntities(semester: Semester) = map { + Subject( + studentId = semester.studentId, + diaryId = semester.diaryId, + name = it.name, + realId = it.id + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt new file mode 100644 index 00000000..49cb7c29 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.sdk.pojo.Teacher as SdkTeacher + +fun List.mapToEntities(semester: Semester) = map { + Teacher( + studentId = semester.studentId, + name = it.name, + subject = it.subject, + shortName = it.short, + classId = semester.classId + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt new file mode 100644 index 00000000..ee525e10 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt @@ -0,0 +1,61 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader +import io.github.wulkanowy.data.pojos.TimetableFull +import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetableFull +import io.github.wulkanowy.sdk.pojo.TimetableDayHeader as SdkTimetableHeader +import io.github.wulkanowy.sdk.pojo.Lesson as SdkLesson +import io.github.wulkanowy.sdk.pojo.LessonAdditional as SdkTimetableAdditional + +fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull( + lessons = lessons.mapToEntities(semester), + additional = additional.mapToEntities(semester), + headers = headers.mapToEntities(semester) +) + +fun List.mapToEntities(semester: Semester) = map { + Timetable( + studentId = semester.studentId, + diaryId = semester.diaryId, + number = it.number, + start = it.start.toInstant(), + end = it.end.toInstant(), + date = it.date, + subject = it.subject, + subjectOld = it.subjectOld, + group = it.group, + room = it.room, + roomOld = it.roomOld, + teacher = it.teacher, + teacherOld = it.teacherOld, + info = it.info, + isStudentPlan = it.studentPlan, + changes = it.changes, + canceled = it.canceled + ) +} + +@JvmName("mapToEntitiesTimetableAdditional") +fun List.mapToEntities(semester: Semester) = map { + TimetableAdditional( + studentId = semester.studentId, + diaryId = semester.diaryId, + subject = it.subject, + date = it.date, + start = it.start.toInstant(), + end = it.end.toInstant(), + ) +} + +@JvmName("mapToEntitiesTimetableHeaders") +fun List.mapToEntities(semester: Semester) = map { + TimetableHeader( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + content = it.content + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt deleted file mode 100644 index d67aa2a7..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/AppCreator.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.wulkanowy.data.pojos - -class AppCreator(val displayName: String, val githubUsername: String) diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt new file mode 100644 index 00000000..5810363c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.pojos + +data class AttendanceData( + val subjectName: String, + val lessonBalance: Int, + val presences: Int, + val absences: Int, +) { + val total: Int + get() = presences + absences + + val presencePercentage: Double + get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt new file mode 100644 index 00000000..4165b3f1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.data.pojos + +import kotlinx.serialization.Serializable + +@Serializable +class Contributor( + val displayName: String, + val githubUsername: String +) diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt index 34b62abd..bdcd049d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt @@ -1,14 +1,25 @@ package io.github.wulkanowy.data.pojos +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.github.wulkanowy.ui.modules.grade.statistics.ViewType +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics data class GradeStatisticsItem( - val type: ViewType, + val type: DataType, - val partial: List, + val average: String, + + val partial: GradePartialStatistics?, + + val semester: GradeSemesterStatistics?, val points: GradePointsStatistics? -) + +) { + enum class DataType { + SEMESTER, + PARTIAL, + POINTS, + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt new file mode 100644 index 00000000..c2b4d2de --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.pojos + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginEvent( + val uuid: String, + val schoolName: String, + val schoolShort: String, + val schoolAddress: String, + val scraperBaseUrl: String, + val symbol: String, + val schoolId: String, + val loginType: String, +) + +@Serializable +data class IntegrityRequest( + val tokenString: String, + val data: T, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt new file mode 100644 index 00000000..2e568e37 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem +import kotlinx.serialization.Serializable + +@Serializable +data class MessageDraft( + val recipients: List, + val subject: String, + val content: String, +) \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt new file mode 100644 index 00000000..f4fd0fc8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.services.sync.notifications.NotificationType +import io.github.wulkanowy.ui.modules.Destination + +data class NotificationData( + val destination: Destination, + val title: String, + val content: String +) + +data class GroupNotificationData( + val notificationDataList: List, + val title: String, + val content: String, + val destination: Destination, + val type: NotificationType +) + diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt new file mode 100644 index 00000000..dec6ebec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.scrapper.Scrapper + +data class RegisterUser( + val email: String, + val password: String?, + val login: String, // may be the same as email + val scrapperBaseUrl: String?, + val loginType: Scrapper.LoginType?, + val loginMode: Sdk.Mode, + val symbols: List, +) : java.io.Serializable + +data class RegisterSymbol( + val symbol: String, + val error: Throwable?, + val hebeBaseUrl: String?, + val keyId: String?, + val privatePem: String?, + val userName: String, + val schools: List, +) : java.io.Serializable + +data class RegisterUnit( + val userLoginId: Int, + val schoolId: String, + val schoolName: String, + val schoolShortName: String, + val parentIds: List, + val studentIds: List, + val employeeIds: List, + val error: Throwable?, + val students: List, +) : java.io.Serializable + +data class RegisterStudent( + val studentId: Int, + val studentName: String, + val studentSecondName: String, + val studentSurname: String, + val className: String, + val classId: Int, + val isParent: Boolean, + val semesters: List, + val isAuthorized: Boolean, + val isEduOne: Boolean +) : java.io.Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/TimetableFull.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/TimetableFull.kt new file mode 100644 index 00000000..ce0f6ef5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/TimetableFull.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader + +data class TimetableFull( + val lessons: List, + val additional: List, + val headers: List, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt new file mode 100644 index 00000000..bec2797d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.repositories + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.utils.DispatchersProvider +import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppCreatorRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val dispatchers: DispatchersProvider, + private val json: Json, +) { + + @OptIn(ExperimentalSerializationApi::class) + suspend fun getAppCreators() = withContext(dispatchers.io) { + val inputStream = context.assets.open("contributors.json").buffered() + json.decodeFromStream>(inputStream) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt new file mode 100644 index 00000000..ebe6f5db --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -0,0 +1,182 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.dao.TimetableDao +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.sdk.pojo.Absent +import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceRepository @Inject constructor( + private val attendanceDb: AttendanceDao, + private val timetableDb: TimetableDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, + private val preferencesRepository: PreferencesRepository +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "attendance" + + private fun filterAttendance( + hiddenAttendanceTiles: List, + attendanceItem: Attendance + ): Boolean { + return when { + attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE + ) -> false + + attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE + ) -> false + + attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS + ) -> false + + attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS + ) -> false + + attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false + attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false + attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false + + else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN) + } + } + + private fun filterAttendance( + hiddenAttendanceTiles: List, + attendanceItem: SdkAttendance + ): Boolean { + return when { + attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE + ) -> false + + attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE + ) -> false + + attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS + ) -> false + + attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains( + DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS + ) -> false + + attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false + attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false + attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false + + else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN) + } + } + + fun getAttendance( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + forceRefresh: Boolean, + notify: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems + + attendanceDb + .loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) + .map { + it.filter { item -> filterAttendance(hiddenAttendanceItems, item) } + } + }, + fetch = { + val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems + + val lessons = timetableDb.load( + semester.diaryId, semester.studentId, start.monday, end.sunday + ) + + wulkanowySdkFactory.create(student, semester) + .getAttendance(start.monday, end.sunday) + .filter { item -> filterAttendance(hiddenAttendanceItems, item) } + .mapToEntities(semester, lessons) + }, + saveFetchResult = { old, new -> + val attendanceToAdd = (new uniqueSubtract old).map { newAttendance -> + newAttendance.apply { if (notify) isNotified = false } + } + attendanceDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = attendanceToAdd, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) + + fun getAttendanceFromDatabase( + semester: Semester, + start: LocalDate, + end: LocalDate + ): Flow> { + val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems + + return attendanceDb + .loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) + .map { + it.filter { item -> filterAttendance(hiddenAttendanceItems, item) } + } + } + + suspend fun updateTimetable(timetable: List) { + return attendanceDb.updateAll(timetable) + } + + suspend fun excuseForAbsence( + student: Student, + semester: Semester, + absenceList: List, + reason: String? = null + ) { + val items = absenceList.map { attendance -> + Absent( + date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)), + timeId = attendance.timeId + ) + } + wulkanowySdkFactory.create(student, semester) + .excuseForAbsence(items, reason) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt new file mode 100644 index 00000000..78c98169 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -0,0 +1,56 @@ +package io.github.wulkanowy.data.repositories + +import androidx.room.withTransaction +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceSummaryRepository @Inject constructor( + private val attendanceDb: AttendanceSummaryDao, + private val refreshHelper: AutoRefreshHelper, + private val appDatabase: AppDatabase, + private val wulkanowySdkFactory: WulkanowySdkFactory, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "attendance_summary" + + fun getAttendanceSummary( + student: Student, + semester: Semester, + subjectId: Int, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getAttendanceSummary(subjectId) + .mapToEntities(semester, subjectId) + }, + saveFetchResult = { old, new -> + appDatabase.withTransaction { + attendanceDb.deleteAll(old uniqueSubtract new) + attendanceDb.insertAll(new uniqueSubtract old) + } + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt new file mode 100644 index 00000000..45a36f55 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -0,0 +1,67 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import java.time.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CompletedLessonsRepository @Inject constructor( + private val completedLessonsDb: CompletedLessonsDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "completed" + + fun getCompletedLessons( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + completedLessonsDb.loadAll( + studentId = semester.studentId, + diaryId = semester.diaryId, + from = start.monday, + end = end.sunday + ) + }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getCompletedLessons(start.monday, end.sunday) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + completedLessonsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt new file mode 100644 index 00000000..58ce0091 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -0,0 +1,71 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.ConferenceDao +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ConferenceRepository @Inject constructor( + private val conferenceDb: ConferenceDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "conference" + + fun getConferences( + student: Student, + semester: Semester, + forceRefresh: Boolean, + notify: Boolean = false, + startDate: Instant = Instant.EPOCH, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) + }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getConferences() + .mapToEntities(semester) + .filter { it.date >= startDate } + }, + saveFetchResult = { old, new -> + conferenceDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + } + ) + + fun getConferenceFromDatabase(semester: Semester): Flow> = + conferenceDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + startDate = Instant.EPOCH, + ) + + suspend fun updateConference(conference: List) = conferenceDb.updateAll(conference) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt new file mode 100644 index 00000000..89dbcd5c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -0,0 +1,85 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.ExamDao +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.endExamsDay +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.startExamsDay +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import java.time.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ExamRepository @Inject constructor( + private val examDb: ExamDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "exam" + + fun getExams( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + forceRefresh: Boolean, + notify: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + examDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start.startExamsDay, + end = start.endExamsDay + ) + }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getExams(start.startExamsDay, start.endExamsDay) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + examDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) + + fun getExamsFromDatabase( + semester: Semester, + start: LocalDate, + end: LocalDate + ): Flow> = examDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start, + end = end, + ) + + suspend fun updateExam(exam: List) = examDb.updateAll(exam) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt new file mode 100644 index 00000000..7a502787 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -0,0 +1,214 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao +import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.toLocalDate +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import timber.log.Timber +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeRepository @Inject constructor( + private val gradeDb: GradeDao, + private val gradeSummaryDb: GradeSummaryDao, + private val gradeDescriptiveDb: GradeDescriptiveDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, + private val preferencesRepository: PreferencesRepository +) { + + private val saveFetchResultMutex = Mutex() + + private fun loadGrades(semesterId: Int, studentId: Int): Flow> { + val hiddenGrades = preferencesRepository.hiddenGrades + + Timber.i("Load grades for semester $semesterId student $studentId") + return gradeDb.loadAllCensored(semesterId, studentId, hiddenGrades.toTypedArray()) + } + + fun getGrades( + student: Student, + semester: Semester, + forceRefresh: Boolean, + notify: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { + //When details is empty and summary is not, app will not use summary cache - edge case + it.first.isEmpty() + }, + shouldFetch = { (details, summaries, descriptive) -> + val isExpired = + refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester)) + details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired + }, + query = { + val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId) + val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) + val descriptiveFlow = + gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId) + + combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive -> + Triple(details, summaries, descriptive) + } + }, + fetch = { + val hiddenGrades = preferencesRepository.hiddenGrades + + val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester) + .getGrades(semester.semesterId) + + val censoredDetails = details.filterNot { it.entry in hiddenGrades } + + Triple( + censoredDetails.mapToEntities(semester), + summary.mapToEntities(semester), + descriptive.mapToEntities(semester) + ) + }, + saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) -> + refreshGradeDetails(student, oldDetails, newDetails, notify) + refreshGradeSummaries(oldSummary, newSummary, notify) + refreshGradeDescriptions(oldDescriptive, newDescriptive, notify) + + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester)) + } + ) + + private suspend fun refreshGradeDescriptions( + old: List, + new: List, + notify: Boolean + ) { + gradeDescriptiveDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) + } + + private suspend fun refreshGradeDetails( + student: Student, + oldGrades: List, + newDetails: List, + notify: Boolean + ) { + val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date + ?: student.registrationDate.toLocalDate() + + gradeDb.removeOldAndSaveNew( + oldItems = oldGrades uniqueSubtract newDetails, + newItems = (newDetails uniqueSubtract oldGrades).onEach { + if (it.date >= notifyBreakDate) it.apply { + isRead = false + if (notify) isNotified = false + } + }, + ) + } + + private suspend fun refreshGradeSummaries( + oldSummaries: List, + newSummary: List, + notify: Boolean + ) { + gradeSummaryDb.removeOldAndSaveNew( + oldItems = oldSummaries uniqueSubtract newSummary, + newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary -> + getGradeSummaryWithUpdatedNotificationState( + summary = summary, + oldSummary = oldSummaries.find { it.subject == summary.subject }, + notify = notify, + ) + }, + ) + } + + private fun getGradeSummaryWithUpdatedNotificationState( + summary: GradeSummary, + oldSummary: GradeSummary?, + notify: Boolean, + ) { + summary.isPredictedGradeNotified = when { + summary.predictedGrade.isEmpty() -> true + notify && oldSummary?.predictedGrade != summary.predictedGrade -> false + else -> true + } + summary.isFinalGradeNotified = when { + summary.finalGrade.isEmpty() -> true + notify && oldSummary?.finalGrade != summary.finalGrade -> false + else -> true + } + summary.predictedGradeLastChange = when { + oldSummary == null -> Instant.now() + summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() + else -> oldSummary.predictedGradeLastChange + } + summary.finalGradeLastChange = when { + oldSummary == null -> Instant.now() + summary.finalGrade != oldSummary.finalGrade -> Instant.now() + else -> oldSummary.finalGradeLastChange + } + } + + fun getUnreadGrades(semester: Semester): Flow> { + return loadGrades(semester.semesterId, semester.studentId).map { + it.filter { grade -> !grade.isRead } + } + } + + fun getGradesFromDatabase(semester: Semester): Flow> { + return loadGrades(semester.semesterId, semester.studentId) + } + + fun getGradesPredictedFromDatabase(semester: Semester): Flow> { + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) + } + + fun getGradesFinalFromDatabase(semester: Semester): Flow> { + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) + } + + fun getGradesDescriptiveFromDatabase(semester: Semester): Flow> { + return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId) + } + + suspend fun updateGrade(grade: Grade) { + return gradeDb.updateAll(listOf(grade)) + } + + suspend fun updateGrades(grades: List) { + return gradeDb.updateAll(grades) + } + + suspend fun updateGradesSummary(gradesSummary: List) { + return gradeSummaryDb.updateAll(gradesSummary) + } + + suspend fun updateGradesDescriptive(gradesDescriptive: List) { + return gradeDescriptiveDb.updateAll(gradesDescriptive) + } + + private companion object { + + private const val GRADE_CACHE_KEY = "grade" + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt new file mode 100644 index 00000000..f120d34f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -0,0 +1,203 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems +import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems +import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import java.util.Locale +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GradeStatisticsRepository @Inject constructor( + private val gradePartialStatisticsDb: GradePartialStatisticsDao, + private val gradePointsStatisticsDb: GradePointsStatisticsDao, + private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val partialMutex = Mutex() + private val semesterMutex = Mutex() + private val pointsMutex = Mutex() + + private val partialCacheKey = "grade_stats_partial" + private val semesterCacheKey = "grade_stats_semester" + private val pointsCacheKey = "grade_stats_points" + + fun getGradesPartialStatistics( + student: Student, + semester: Semester, + subjectName: String, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = partialMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(partialCacheKey, semester) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getGradesPartialStatistics(semester.semesterId) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + gradePartialStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester)) + }, + mapResult = { items -> + when (subjectName) { + "Wszystkie" -> { + val summaryItem = GradePartialStatistics( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = subjectName, + classAverage = items.map { it.classAverage }.getSummaryAverage(), + studentAverage = items.map { it.studentAverage }.getSummaryAverage(), + classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), + studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() + ) + listOf(summaryItem) + items + } + + else -> items.filter { it.subject == subjectName } + }.mapPartialToStatisticItems() + } + ) + + fun getGradesSemesterStatistics( + student: Student, + semester: Semester, + subjectName: String, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = semesterMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(semesterCacheKey, semester) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getGradesSemesterStatistics(semester.semesterId) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + gradeSemesterStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester)) + }, + mapResult = { items -> + val itemsWithAverage = items.map { item -> + item.copy().apply { + val denominator = item.amounts.sum() + classAverage = if (denominator == 0) "" else { + (item.amounts.mapIndexed { gradeValue, amount -> + (gradeValue + 1) * amount + }.sum().toDouble() / denominator).asAverageString() + } + } + } + when (subjectName) { + "Wszystkie" -> { + val summaryItem = GradeSemesterStatistics( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = subjectName, + amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), + studentGrade = 0, + ).apply { + classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage() + studentAverage = items + .mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } } + .average().asAverageString() + } + listOf(summaryItem) + itemsWithAverage + } + + else -> itemsWithAverage.filter { it.subject == subjectName } + }.mapSemesterToStatisticItems() + } + ) + + fun getGradesPointsStatistics( + student: Student, + semester: Semester, + subjectName: String, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = pointsMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getGradesPointsStatistics(semester.semesterId) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + gradePointsStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester)) + }, + mapResult = { items -> + when (subjectName) { + "Wszystkie" -> items + else -> items.filter { it.subject == subjectName } + }.mapPointsToStatisticsItems() + } + ) + + private fun List.getSummaryAverage(): String { + val averages = mapNotNull { + it.replace(",", ".").toDoubleOrNull() + } + + return averages.average() + .asAverageString() + .takeIf { averages.isNotEmpty() } + .orEmpty() + } + + private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this) + + private fun List>.sumGradeAmounts(): List { + val result = mutableListOf(0, 0, 0, 0, 0, 0) + forEach { + it.forEachIndexed { grade, amount -> + result[grade] += amount + } + } + return result + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt new file mode 100644 index 00000000..7893ef63 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -0,0 +1,87 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.HomeworkDao +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import java.time.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class HomeworkRepository @Inject constructor( + private val homeworkDb: HomeworkDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "homework" + + fun getHomework( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + forceRefresh: Boolean, + notify: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(cacheKey, semester, start, end) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + homeworkDb.loadAll( + semesterId = semester.semesterId, + studentId = semester.studentId, + from = start.monday, + end = end.sunday + ) + }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getHomework(start.monday, end.sunday) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + val filteredOld = old.filterNot { it.isAddedByUser } + + homeworkDb.removeOldAndSaveNew( + oldItems = filteredOld uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + } + ) + + suspend fun toggleDone(homework: Homework) { + homeworkDb.updateAll(listOf(homework.apply { + isDone = !isDone + })) + } + + fun getHomeworkFromDatabase(semester: Semester, start: LocalDate, end: LocalDate) = + homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) + + suspend fun updateHomework(homework: List) = homeworkDb.updateAll(homework) + + suspend fun saveHomework(homework: Homework) = homeworkDb.insertAll(listOf(homework)) + + suspend fun deleteHomework(homework: Homework) = homeworkDb.deleteAll(listOf(homework)) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt new file mode 100644 index 00000000..1a8cd6ea --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt @@ -0,0 +1,37 @@ +package io.github.wulkanowy.data.repositories + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.utils.DispatchersProvider +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileNotFoundException +import javax.inject.Inject + +class LoggerRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val dispatchers: DispatchersProvider +) { + + suspend fun getLastLogLines() = getLastModified().readText().split("\n") + + suspend fun getLogFiles() = withContext(dispatchers.io) { + File(context.filesDir.absolutePath).listFiles(File::isFile) + ?.filter { it.name.endsWith(".log") }!! + } + + private suspend fun getLastModified() = withContext(dispatchers.io) { + var lastModifiedTime = Long.MIN_VALUE + var chosenFile: File? = null + + File(context.filesDir.absolutePath).listFiles(File::isFile) + ?.forEach { file -> + if (file.lastModified() > lastModifiedTime) { + lastModifiedTime = file.lastModified() + chosenFile = file + } + } + + chosenFile ?: throw FileNotFoundException("Log file not found") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt new file mode 100644 index 00000000..dfafe6c8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider +import io.github.wulkanowy.utils.AppWidgetUpdater +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import java.time.LocalDate +import java.time.LocalDate.now +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LuckyNumberRepository @Inject constructor( + private val luckyNumberDb: LuckyNumberDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val appWidgetUpdater: AppWidgetUpdater, +) { + + private val saveFetchResultMutex = Mutex() + + fun getLuckyNumber( + student: Student, + forceRefresh: Boolean, + notify: Boolean = false, + isFromAppWidget: Boolean = false + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, + shouldFetch = { it == null || forceRefresh }, + query = { luckyNumberDb.load(student.studentId, now()) }, + fetch = { + wulkanowySdkFactory.create(student) + .getLuckyNumber(student.schoolShortName) + ?.mapToEntity(student) + }, + saveFetchResult = { oldLuckyNumber, newLuckyNumber -> + newLuckyNumber ?: return@networkBoundResource + + if (newLuckyNumber != oldLuckyNumber) { + luckyNumberDb.removeOldAndSaveNew( + oldItems = listOfNotNull(oldLuckyNumber), + newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }), + ) + if (!isFromAppWidget) { + appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class) + } + } + } + ) + + fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) = + luckyNumberDb.getAll(student.studentId, start, end) + + suspend fun getNotNotifiedLuckyNumber(student: Student) = + luckyNumberDb.load(student.studentId, now()).map { + if (it?.isNotified == false) it else null + }.first() + + suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = + luckyNumberDb.updateAll(listOfNotNull(luckyNumber)) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt new file mode 100644 index 00000000..f91dc63e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt @@ -0,0 +1,286 @@ +package io.github.wulkanowy.data.repositories + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.dao.MailboxDao +import io.github.wulkanowy.data.db.dao.MessageAttachmentDao +import io.github.wulkanowy.data.db.dao.MessagesDao +import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor +import io.github.wulkanowy.data.db.entities.MutedMessageSender +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.enums.MessageFolder.SENT +import io.github.wulkanowy.data.enums.MessageFolder.TRASHED +import io.github.wulkanowy.data.mappers.mapFromEntities +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.pojos.MessageDraft +import io.github.wulkanowy.data.toFirstResult +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase +import io.github.wulkanowy.sdk.pojo.Folder +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MessageRepository @Inject constructor( + private val messagesDb: MessagesDao, + private val mutedMessageSendersDao: MutedMessageSendersDao, + private val messageAttachmentDao: MessageAttachmentDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + @ApplicationContext private val context: Context, + private val refreshHelper: AutoRefreshHelper, + private val sharedPrefProvider: SharedPrefProvider, + private val json: Json, + private val mailboxDao: MailboxDao, + private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase, +) { + private val saveFetchResultMutex = Mutex() + + private val messagesCacheKey = "message" + private val mailboxCacheKey = "mailboxes" + + fun getMessages( + student: Student, + mailbox: Mailbox?, + folder: MessageFolder, + forceRefresh: Boolean, + notify: Boolean = false, + ): Flow>> = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(messagesCacheKey, mailbox, folder) + ) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + if (mailbox == null) { + messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email) + } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id) + }, + fetch = { + wulkanowySdkFactory.create(student) + .getMessages( + folder = Folder.valueOf(folder.name), + mailboxKey = mailbox?.globalKey, + ) + .mapToEntities( + student = student, + mailbox = mailbox, + allMailboxes = mailboxDao.loadAll(student.email) + ) + }, + saveFetchResult = { oldWithAuthors, new -> + val old = oldWithAuthors.map { it.message } + messagesDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + val muted = isMuted(it.correspondents) + it.isNotified = !notify || muted + }, + ) + refreshHelper.updateLastRefreshTimestamp( + getRefreshKey(messagesCacheKey, mailbox, folder) + ) + } + ) + + fun getMessage( + student: Student, + message: Message, + markAsRead: Boolean = false, + ): Flow> = networkBoundResource( + isResultEmpty = { it?.message?.content.isNullOrBlank() }, + shouldFetch = { + checkNotNull(it) { "This message no longer exist!" } + Timber.d("Message content in db empty: ${it.message.content.isBlank()}") + (it.message.unread && markAsRead) || it.message.content.isBlank() + }, + query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, + fetch = { + wulkanowySdkFactory.create(student) + .getMessageDetails( + messageKey = message.messageGlobalKey, + markAsRead = message.unread && markAsRead, + ) + }, + saveFetchResult = { old, new -> + checkNotNull(old) { "Fetched message no longer exist!" } + messagesDb.updateAll( + listOf(old.message.apply { + id = message.id + unread = when { + markAsRead -> false + else -> unread + } + sender = new.sender + recipients = new.recipients.singleOrNull() ?: "Wielu adresatów" + content = content.ifBlank { new.content } + }) + ) + messageAttachmentDao.insertAttachments( + items = new.attachments.mapToEntities(message.messageGlobalKey), + ) + + Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read: $markAsRead") + } + ) + + fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow> { + return if (mailbox == null) { + messagesDb.loadAll(RECEIVED.id, student.email) + } else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id) + } + + suspend fun updateMessages(messages: List) { + return messagesDb.updateAll(messages) + } + + suspend fun sendMessage( + student: Student, + subject: String, + content: String, + recipients: List, + mailbox: Mailbox, + ) { + wulkanowySdkFactory.create(student) + .sendMessage( + subject = subject, + content = content, + recipients = recipients.mapFromEntities(), + mailboxId = mailbox.globalKey, + ) + refreshFolders(student, mailbox, listOf(SENT)) + } + + suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) { + wulkanowySdkFactory.create(student) + .restoreMessages(messages = messages.map { it.messageGlobalKey }) + + refreshFolders(student, mailbox) + } + + suspend fun deleteMessage(student: Student, message: Message) { + deleteMessages(student, listOf(message)) + } + + suspend fun deleteMessages(student: Student, messages: List) { + val firstMessage = messages.first() + wulkanowySdkFactory.create(student) + .deleteMessages( + messages = messages.map { it.messageGlobalKey }, + removeForever = firstMessage.folderId == TRASHED.id, + ) + + if (firstMessage.folderId != TRASHED.id) { + val deletedMessages = messages.map { + it.copy(folderId = TRASHED.id) + .apply { + id = it.id + content = it.content + sender = it.sender + recipients = it.recipients + } + } + + messagesDb.updateAll(deletedMessages) + } else { + messagesDb.deleteAll(messages) + } + } + + private suspend fun refreshFolders( + student: Student, + mailbox: Mailbox?, + folders: List = MessageFolder.entries + ) { + folders.forEach { + getMessages( + student = student, + mailbox = mailbox, + folder = it, + forceRefresh = true, + ).toFirstResult() + } + } + + suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(mailboxCacheKey, student), + ) + it.isEmpty() || isExpired || forceRefresh + }, + query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, + fetch = { + wulkanowySdkFactory.create(student) + .getMailboxes() + .mapToEntities(student) + }, + saveFetchResult = { old, new -> + mailboxDao.deleteAll(old uniqueSubtract new) + mailboxDao.insertAll(new uniqueSubtract old) + + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student)) + } + ) + + suspend fun getMailboxByStudent(student: Student): Mailbox? { + val mailbox = getMailboxByStudentUseCase(student) + + return if (mailbox == null) { + getMailboxes(student, forceRefresh = true) + .onResourceError { throw it } + .onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") } + .waitForResult() + + getMailboxByStudentUseCase(student) + } else mailbox + } + + var draftMessage: MessageDraft? + get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft)) + ?.let { json.decodeFromString(it) } + set(value) = sharedPrefProvider.putString( + context.getString(R.string.pref_key_message_draft), + value?.let { json.encodeToString(it) } + ) + + private suspend fun isMuted(author: String): Boolean { + return mutedMessageSendersDao.checkMute(author) + } + + suspend fun muteMessage(author: String) { + if (isMuted(author)) return + mutedMessageSendersDao.insertMute(MutedMessageSender(author)) + } + + suspend fun unmuteMessage(author: String) { + if (!isMuted(author)) return + mutedMessageSendersDao.deleteMute(author) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt new file mode 100644 index 00000000..1303d0e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MobileDeviceRepository @Inject constructor( + private val mobileDb: MobileDeviceDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "devices" + + fun getDevices( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { mobileDb.loadAll(student.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getRegisteredDevices() + .mapToEntities(student) + }, + saveFetchResult = { old, new -> + mobileDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) + } + ) + + suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { + wulkanowySdkFactory.create(student, semester) + .unregisterDevice(device.deviceId) + + mobileDb.deleteAll(listOf(device)) + } + + suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { + return wulkanowySdkFactory.create(student, semester) + .getToken() + .mapToMobileDeviceToken() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt new file mode 100644 index 00000000..6be0928e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -0,0 +1,81 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.NoteDao +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.toLocalDate +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NoteRepository @Inject constructor( + private val noteDb: NoteDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, + private val preferencesRepository: PreferencesRepository +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "note" + + fun getNotes( + student: Student, + semester: Semester, + forceRefresh: Boolean, + notify: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + getRefreshKey(cacheKey, semester) + ) + + it.isEmpty() || forceRefresh || isExpired + }, + query = { noteDb.loadAll(student.studentId) }, + fetch = { + val showNotes = preferencesRepository.showNotes + + wulkanowySdkFactory.create(student, semester) + .getNotes() + .filter { showNotes } + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + val notesToAdd = (new uniqueSubtract old).onEach { + if (it.date >= student.registrationDate.toLocalDate()) it.apply { + isRead = false + if (notify) isNotified = false + } + } + noteDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = notesToAdd, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + } + ) + + fun getNotesFromDatabase(student: Student): Flow> { + return noteDb.loadAll(student.studentId) + } + + suspend fun updateNote(note: Note) { + noteDb.updateAll(listOf(note)) + } + + suspend fun updateNotes(notes: List) { + noteDb.updateAll(notes) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NotificationRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NotificationRepository.kt new file mode 100644 index 00000000..fca26378 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NotificationRepository.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.NotificationDao +import io.github.wulkanowy.data.db.entities.Notification +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NotificationRepository @Inject constructor( + private val notificationDao: NotificationDao, +) { + + fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId) + + suspend fun saveNotification(notification: Notification) = + notificationDao.insertAll(listOf(notification)) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt new file mode 100644 index 00000000..0cc883a0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -0,0 +1,504 @@ +package io.github.wulkanowy.data.repositories + +import android.content.Context +import android.content.SharedPreferences +import androidx.annotation.StringRes +import androidx.core.content.edit +import com.fredporciuncula.flow.preferences.FlowSharedPreferences +import com.fredporciuncula.flow.preferences.Preference +import com.fredporciuncula.flow.preferences.Serializer +import com.fredporciuncula.flow.preferences.map +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.api.models.Mapping +import io.github.wulkanowy.data.enums.AppTheme +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.enums.GradeExpandMode +import io.github.wulkanowy.data.enums.GradeSortingMode +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode +import io.github.wulkanowy.data.enums.TimetableGapsMode +import io.github.wulkanowy.data.enums.TimetableMode +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode +import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import timber.log.Timber +import java.time.Instant +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PreferencesRepository @Inject constructor( + @ApplicationContext val context: Context, + private val sharedPref: SharedPreferences, + private val flowSharedPref: FlowSharedPreferences, + private val json: Json, +) { + private val NO_ATTENDANCE_VALUE = -1.0 + + val isShowPresent: Boolean + get() = getBoolean( + R.string.pref_key_attendance_present, + R.bool.pref_default_attendance_present + ) + + val targetAttendanceFlow: Flow + get() = flowSharedPref.getInt( + context.getString(R.string.pref_key_attendance_target), + context.resources.getInteger(R.integer.pref_default_attendance_target) + ).asFlow() + + val attendanceCalculatorSortingModeFlow: Flow + get() = flowSharedPref.getString( + context.getString(R.string.pref_key_attendance_calculator_sorting_mode), + context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode) + ).asFlow().map(AttendanceCalculatorSortingMode::getByValue) + + /** + * Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0). + */ + val attendanceCalculatorShowEmptySubjects: Flow + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects), + context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects) + ).asFlow() + + private val gradeAverageModePref: Preference + get() = getObjectFlow( + R.string.pref_key_grade_average_mode, + R.string.pref_default_grade_average_mode, + object : Serializer { + override fun serialize(value: GradeAverageMode) = value.value + override fun deserialize(serialized: String) = + GradeAverageMode.getByValue(serialized) + }, + ) + + val gradeAverageModeFlow: Flow + get() = gradeAverageModePref.asFlow() + + private val gradeAverageForceCalcPref: Preference + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_grade_average_force_calc), + context.resources.getBoolean(R.bool.pref_default_grade_average_force_calc) + ) + + val gradeAverageForceCalcFlow: Flow + get() = gradeAverageForceCalcPref.asFlow() + + val gradeExpandMode: GradeExpandMode + get() = GradeExpandMode.getByValue( + getString( + R.string.pref_key_expand_grade_mode, + R.string.pref_default_expand_grade_mode + ) + ) + + val showAllSubjectsOnStatisticsList: Boolean + get() = getBoolean( + R.string.pref_key_grade_statistics_list, + R.bool.pref_default_grade_statistics_list + ) + + val appThemeKey = context.getString(R.string.pref_key_app_theme) + val appTheme: AppTheme + get() = AppTheme.getByValue(getString(appThemeKey, R.string.pref_default_app_theme)) + + val gradeColorTheme: GradeColorTheme + get() = GradeColorTheme.getByValue( + getString( + R.string.pref_key_grade_color_scheme, + R.string.pref_default_grade_color_scheme + ) + ) + + val appLanguageKey = context.getString(R.string.pref_key_app_language) + val appLanguage + get() = getString(appLanguageKey, R.string.pref_default_app_language) + + val serviceEnableKey = context.getString(R.string.pref_key_services_enable) + val isServiceEnabled: Boolean + get() = getBoolean(serviceEnableKey, R.bool.pref_default_services_enable) + + val servicesIntervalKey = context.getString(R.string.pref_key_services_interval) + val servicesInterval: Long + get() = getString(servicesIntervalKey, R.string.pref_default_services_interval).toLong() + + val servicesOnlyWifiKey = context.getString(R.string.pref_key_services_wifi_only) + val isServicesOnlyWifi: Boolean + get() = getBoolean(servicesOnlyWifiKey, R.bool.pref_default_services_wifi_only) + + val notificationsEnableKey = context.getString(R.string.pref_key_notifications_enable) + val isNotificationsEnable: Boolean + get() = getBoolean(notificationsEnableKey, R.bool.pref_default_notifications_enable) + + val isUpcomingLessonsNotificationsEnableKey = + context.getString(R.string.pref_key_notifications_upcoming_lessons_enable) + var isUpcomingLessonsNotificationsEnable: Boolean + set(value) { + sharedPref.edit { putBoolean(isUpcomingLessonsNotificationsEnableKey, value) } + } + get() = getBoolean( + isUpcomingLessonsNotificationsEnableKey, + R.bool.pref_default_notification_upcoming_lessons_enable + ) + + val isUpcomingLessonsNotificationsPersistentKey = + context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent) + val isUpcomingLessonsNotificationsPersistent: Boolean + get() = getBoolean( + isUpcomingLessonsNotificationsPersistentKey, + R.bool.pref_default_notification_upcoming_lessons_persistent + ) + + val isNotificationPiggybackEnabledKey = + context.getString(R.string.pref_key_notifications_piggyback) + val isNotificationPiggybackEnabled: Boolean + get() = getBoolean( + R.string.pref_key_notifications_piggyback, + R.bool.pref_default_notification_piggyback + ) + + val isNotificationPiggybackRemoveOriginalEnabled: Boolean + get() = getBoolean( + R.string.pref_key_notifications_piggyback_cancel_original, + R.bool.pref_default_notification_piggyback_cancel_original + ) + + val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug) + val isDebugNotificationEnable: Boolean + get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug) + + val gradePlusModifier: Double + get() = getString( + R.string.pref_key_grade_modifier_plus, + R.string.pref_default_grade_modifier_plus + ).toDouble() + + val gradePlusModifierFlow: Flow + get() = getStringFlow( + R.string.pref_key_grade_modifier_plus, + R.string.pref_default_grade_modifier_plus + ).asFlow().map { it.toDouble() } + + val gradeMinusModifier: Double + get() = getString( + R.string.pref_key_grade_modifier_minus, + R.string.pref_default_grade_modifier_minus + ).toDouble() + + val gradeMinusModifierFlow: Flow + get() = getStringFlow( + R.string.pref_key_grade_modifier_minus, + R.string.pref_default_grade_modifier_minus + ).asFlow().map { it.toDouble() } + + val fillMessageContent: Boolean + get() = getBoolean( + R.string.pref_key_fill_message_content, + R.bool.pref_default_fill_message_content + ) + + val showGroupsInPlan: Boolean + get() = getBoolean( + R.string.pref_key_timetable_show_groups, + R.bool.pref_default_timetable_show_groups + ) + + val showWholeClassPlan: TimetableMode + get() = TimetableMode.getByValue( + getString( + R.string.pref_key_timetable_show_whole_class, + R.string.pref_default_timetable_show_whole_class + ) + ) + + val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode + get() = getString( + R.string.pref_key_timetable_show_additional_lessons, + R.string.pref_default_timetable_show_additional_lessons + ).let { ShowAdditionalLessonsMode.getByValue(it) } + + val gradeSortingMode: GradeSortingMode + get() = GradeSortingMode.getByValue( + getString( + R.string.pref_key_grade_sorting_mode, + R.string.pref_default_grade_sorting_mode + ) + ) + + val showTimetableGaps: TimetableGapsMode + get() = TimetableGapsMode.getByValue( + getString( + R.string.pref_key_timetable_show_gaps, + R.string.pref_default_timetable_show_gaps + ) + ) + + val showSubjectsWithoutGrades: Boolean + get() = getBoolean( + R.string.pref_key_subjects_without_grades, + R.bool.pref_default_subjects_without_grades + ) + + val isOptionalArithmeticAverageFlow: Flow + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_optional_arithmetic_average), + context.resources.getBoolean(R.bool.pref_default_optional_arithmetic_average) + ).asFlow() + + var lasSyncDate: Instant? + get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) + .takeIf { it != 0L }?.let(Instant::ofEpochMilli) + set(value) = sharedPref.edit().putLong("last_sync_date", value?.toEpochMilli() ?: 0).apply() + + var dashboardItemsPosition: Map? + get() { + val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null + + return json.decodeFromString(value) + } + set(value) = sharedPref.edit { + putString( + PREF_KEY_DASHBOARD_ITEMS_POSITION, + json.encodeToString(value) + ) + } + + val selectedDashboardTilesFlow: Flow> + get() = selectedDashboardTilesPreference.asFlow() + .map { set -> + set.map { DashboardItem.Tile.valueOf(it) } + .plus( + listOfNotNull( + DashboardItem.Tile.ACCOUNT, + DashboardItem.Tile.ADMIN_MESSAGE, + DashboardItem.Tile.ADS.takeIf { isAdsEnabled } + ) + ) + .toSet() + } + + var selectedDashboardTiles: Set + get() = selectedDashboardTilesPreference.get() + .map { DashboardItem.Tile.valueOf(it) } + .plus( + listOfNotNull( + DashboardItem.Tile.ACCOUNT, + DashboardItem.Tile.ADMIN_MESSAGE, + DashboardItem.Tile.ADS.takeIf { isAdsEnabled } + ) + ) + .toSet() + set(value) { + val filteredValue = value.filterNot { + it == DashboardItem.Tile.ACCOUNT || it == DashboardItem.Tile.ADMIN_MESSAGE + } + .map { it.name } + .toSet() + + selectedDashboardTilesPreference.set(filteredValue) + } + + var attendancePercentage: Double? + get() = attendancePercentagePreference.get().takeIf { it != NO_ATTENDANCE_VALUE } + set(value) = attendancePercentagePreference.set(value ?: NO_ATTENDANCE_VALUE) + + var hiddenAttendanceItems: List + get() = hiddenAttendanceItemsPreference.get().toList() + set(value) = hiddenAttendanceItemsPreference.set(value.toSet()) + + var hiddenGrades: List + get() = hiddenGradesPreference.get().toList() + set(value) = hiddenGradesPreference.set(value.toSet()) + + var showNotes: Boolean + get() = showNotesPreference.get() + set(value) = showNotesPreference.set(value) + + var developerMode: Boolean + get() = developerModePreference.get() + set(value) = developerModePreference.set(value) + + private val developerModePreference: Preference + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_developer_mode), + context.resources.getBoolean(R.bool.pref_default_developer_mode) + ) + + private val hiddenGradesPreference: Preference> + get() { + val defaultSet = context.resources.getStringArray(R.array.pref_default_hidden_grades).toSet() + val prefKey = "hidden_grades" + + return flowSharedPref.getStringSet(prefKey, defaultSet) + } + + private val showNotesPreference: Preference + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_show_notes), + context.resources.getBoolean(R.bool.pref_default_show_notes) + ) + + private val hiddenAttendanceItemsPreference: Preference> + get() { + val defaultSet = + context.resources.getStringArray(R.array.pref_default_hidden_attendance_items).toSet() + val prefKey = "attendance_items" + + return flowSharedPref + .getStringSet(prefKey, defaultSet) + .map( + mapper = { it -> it.map { DashboardItem.HiddenAttendanceTile.valueOf(it) }.toSet() }, + reverse = { it -> it.map { it.name }.toSet() } + ) + } + + private val selectedDashboardTilesPreference: Preference> + get() { + val defaultSet = + context.resources.getStringArray(R.array.pref_default_dashboard_tiles).toSet() + val prefKey = context.getString(R.string.pref_key_dashboard_tiles) + + return flowSharedPref.getStringSet(prefKey, defaultSet) + } + + private val attendancePercentagePreference: Preference + get() { + val prefKey = context.getString(R.string.pref_key_attendance_percentage) + val defaultValue = context.resources.getString(R.string.pref_default_attendance_percentage) + + return flowSharedPref + .getString(prefKey, defaultValue) + .map( + mapper = { it.toDoubleOrNull() ?: NO_ATTENDANCE_VALUE }, + reverse = { it.toString() } + ) + } + + var dismissedAdminMessageIds: List + get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet()) + .orEmpty() + .map { it.toInt() } + set(value) = sharedPref.edit { + putStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, value.map { it.toString() }.toSet()) + } + + var inAppReviewCount: Int + get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0) + set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply() + + var inAppReviewDate: Instant? + get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L } + ?.let(Instant::ofEpochMilli) + set(value) = sharedPref.edit { + putLong(PREF_KEY_IN_APP_REVIEW_DATE, value?.toEpochMilli() ?: 0) + } + + var isAppReviewDone: Boolean + get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) + set(value) = sharedPref.edit { putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value) } + + var isAppSupportShown: Boolean + get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false) + set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) } + + val isAdsEnabledFlow = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_ads_enabled), + context.resources.getBoolean(R.bool.pref_default_ads_enabled) + ).asFlow() + + var isAdsEnabled: Boolean + get() = getBoolean( + R.string.pref_key_ads_enabled, + R.bool.pref_default_ads_enabled + ) + set(value) = sharedPref.edit { + putBoolean(context.getString(R.string.pref_key_ads_enabled), value) + } + + var appMenuItemOrder: List + get() { + val value = sharedPref.getString(PREF_KEY_APP_MENU_ITEM_ORDER, null) + ?: return AppMenuItem.defaultAppMenuItemList + + return json.decodeFromString(value) + } + set(value) = sharedPref.edit { + putString( + PREF_KEY_APP_MENU_ITEM_ORDER, + json.encodeToString(value) + ) + } + + var isIncognitoMode: Boolean + get() = getBoolean(R.string.pref_key_incognito_moge, R.bool.pref_default_incognito_mode) + set(value) = sharedPref.edit { + putBoolean(context.getString(R.string.pref_key_incognito_moge), value) + } + + var installationId: String + get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() + private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } + + var mapping: Mapping? + get() { + val value = sharedPref.getString("mapping", null) + return value?.let { json.decodeFromString(it) } + } + set(value) = sharedPref.edit(commit = true) { + putString("mapping", value?.let { json.encodeToString(it) }) + } + + init { + if (installationId.isEmpty()) { + installationId = UUID.randomUUID().toString() + } + } + + private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) + + private fun getLong(id: String, default: Int) = + sharedPref.getLong(id, context.resources.getString(default).toLong()) + + private fun getStringFlow(id: Int, default: Int) = + flowSharedPref.getString(context.getString(id), context.getString(default)) + + private fun getObjectFlow( + @StringRes id: Int, + @StringRes default: Int, + serializer: Serializer + ): Preference = flowSharedPref.getObject( + key = context.getString(id), + serializer = serializer, + defaultValue = serializer.deserialize( + flowSharedPref.getString(context.getString(default)).get() + ) + ) + + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) + + private fun getString(id: String, default: Int) = + sharedPref.getString(id, context.getString(default)) ?: context.getString(default) + + private fun getBoolean(id: Int, default: Int) = getBoolean(context.getString(id), default) + + private fun getBoolean(id: String, default: Int) = + sharedPref.getBoolean(id, context.resources.getBoolean(default)) + + private companion object { + private const val PREF_KEY_APP_MENU_ITEM_ORDER = "app_menu_item_order" + private const val PREF_KEY_INSTALLATION_ID = "installation_id" + private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" + private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" + private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" + private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" + private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown" + private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt new file mode 100644 index 00000000..8233d932 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -0,0 +1,69 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.RecipientDao +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecipientRepository @Inject constructor( + private val recipientDb: RecipientDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val cacheKey = "recipient" + + suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) { + val new = wulkanowySdkFactory.create(student) + .getRecipients(mailbox.globalKey) + .mapToEntities(mailbox.globalKey) + val old = recipientDb.loadAll(type, mailbox.globalKey) + + recipientDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) + } + + suspend fun getRecipients( + student: Student, + mailbox: Mailbox?, + type: MailboxType, + ): List { + mailbox ?: return emptyList() + + val cached = recipientDb.loadAll(type, mailbox.globalKey) + + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) + return if (cached.isEmpty() || isExpired) { + refreshRecipients(student, mailbox, type) + recipientDb.loadAll(type, mailbox.globalKey) + } else cached + } + + suspend fun getMessageSender( + student: Student, + mailbox: Mailbox?, + message: Message, + ): List { + mailbox ?: return emptyList() + + return wulkanowySdkFactory.create(student) + .getMessageReplayDetails(message.messageGlobalKey) + .sender + .let(::listOf) + .mapToEntities(mailbox.globalKey) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt new file mode 100644 index 00000000..b554bda0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecoverRepository @Inject constructor( + private val wulkanowySdkFactory: WulkanowySdkFactory +) { + + suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair = + wulkanowySdkFactory.create() + .getPasswordResetCaptchaCode(host, symbol) + + suspend fun sendRecoverRequest( + url: String, + symbol: String, + email: String, + reCaptchaResponse: String + ): String = wulkanowySdkFactory.create() + .sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt new file mode 100644 index 00000000..78d95699 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SchoolAnnouncementRepository @Inject constructor( + private val schoolAnnouncementDb: SchoolAnnouncementDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "school_announcement" + + fun getSchoolAnnouncements( + student: Student, + forceRefresh: Boolean, + notify: Boolean = false + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { + schoolAnnouncementDb.loadAll(student.studentId) + }, + fetch = { + val sdk = wulkanowySdkFactory.create(student) + val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student) + val directorInformation = sdk.getDirectorInformation().mapToEntities(student) + lastAnnouncements + directorInformation + }, + saveFetchResult = { old, new -> + schoolAnnouncementDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) + } + ) + + fun getSchoolAnnouncementFromDatabase(student: Student): Flow> { + return schoolAnnouncementDb.loadAll(student.studentId) + } + + suspend fun updateSchoolAnnouncement(schoolAnnouncement: List) = + schoolAnnouncementDb.updateAll(schoolAnnouncement) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt new file mode 100644 index 00000000..c48abb6f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -0,0 +1,57 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.SchoolDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SchoolRepository @Inject constructor( + private val schoolDb: SchoolDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "school_info" + + fun getSchoolInfo( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(cacheKey, student) + ) + it == null || forceRefresh || isExpired + }, + query = { schoolDb.load(semester.studentId, semester.classId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getSchool() + .mapToEntity(semester) + }, + saveFetchResult = { old, new -> + if (old != null && new != old) { + schoolDb.removeOldAndSaveNew( + oldItems = listOf(old), + newItems = listOf(new) + ) + } else if (old == null) { + schoolDb.insertAll(listOf(new)) + } + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt new file mode 100644 index 00000000..25da241a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt @@ -0,0 +1,62 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.api.services.SchoolsService +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.IntegrityRequest +import io.github.wulkanowy.data.pojos.LoginEvent +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.utils.IntegrityHelper +import io.github.wulkanowy.utils.getCurrentOrLast +import kotlinx.coroutines.withTimeout +import timber.log.Timber +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.time.Duration.Companion.seconds + +@Singleton +class SchoolsRepository @Inject constructor( + private val integrityHelper: IntegrityHelper, + private val schoolsService: SchoolsService, + private val wulkanowySdkFactory: WulkanowySdkFactory, +) { + + suspend fun logSchoolLogin(loginData: LoginData, students: List) { + students.forEach { + runCatching { + withTimeout(10.seconds) { + logLogin(loginData, it.student, it.semesters.getCurrentOrLast()) + } + } + .onFailure { Timber.e(it) } + } + } + + private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) { + val requestId = UUID.randomUUID().toString() + val token = integrityHelper.getIntegrityToken(requestId) ?: return + val updatedStudent = student.copy(password = loginData.password) + + val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester) + .getSchool() + + schoolsService.logLoginEvent( + IntegrityRequest( + tokenString = token, + data = LoginEvent( + uuid = requestId, + schoolAddress = schoolInfo.address, + schoolName = schoolInfo.name, + schoolShort = student.schoolShortName, + scraperBaseUrl = student.scrapperBaseUrl, + loginType = student.loginType, + symbol = student.symbol, + schoolId = student.schoolSymbol, + ) + ) + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt new file mode 100644 index 00000000..92d44650 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt @@ -0,0 +1,83 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.getCurrentOrLast +import io.github.wulkanowy.utils.isCurrent +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SemesterRepository @Inject constructor( + private val semesterDb: SemesterDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val dispatchers: DispatchersProvider, +) { + + suspend fun getSemesters( + student: Student, + forceRefresh: Boolean = false, + refreshOnNoCurrent: Boolean = false + ) = withContext(dispatchers.io) { + val semesters = semesterDb.loadAll(student.studentId, student.classId) + + if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { + refreshSemesters(student) + semesterDb.loadAll(student.studentId, student.classId) + } else semesters + } + + private fun isShouldFetch( + student: Student, + semesters: List, + forceRefresh: Boolean, + refreshOnNoCurrent: Boolean + ): Boolean { + val isNoSemesters = semesters.isEmpty() + + val isRefreshOnModeChangeRequired = when { + Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE -> { + semesters.firstOrNull { it.isCurrent() }?.let { + 0 == it.diaryId && 0 == it.kindergartenDiaryId + } == true + } + + else -> false + } + + val isRefreshOnNoCurrentAppropriate = + refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent() } + + return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate + } + + private suspend fun refreshSemesters(student: Student) { + val new = wulkanowySdkFactory.create(student) + .getSemesters() + .mapToEntities(student.studentId) + + if (new.isEmpty()) { + Timber.i("Empty semester list from SDK!") + return + } + + val old = semesterDb.loadAll(student.studentId, student.classId) + semesterDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + } + + suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = + withContext(dispatchers.io) { + getSemesters(student, forceRefresh).getCurrentOrLast() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt new file mode 100644 index 00000000..db4c0aeb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.StudentInfoDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class StudentInfoRepository @Inject constructor( + private val studentInfoDao: StudentInfoDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, +) { + + private val saveFetchResultMutex = Mutex() + + fun getStudentInfo( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, + shouldFetch = { it == null || forceRefresh }, + query = { studentInfoDao.loadStudentInfo(student.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getStudentInfo() + .mapToEntity(semester) + }, + saveFetchResult = { old, new -> + if (old != null && new != old) { + studentInfoDao.removeOldAndSaveNew( + oldItems = listOf(old), + newItems = listOf(new), + ) + } else if (old == null) { + studentInfoDao.insertAll(listOf(new)) + } + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt new file mode 100644 index 00000000..575ca89f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -0,0 +1,241 @@ +package io.github.wulkanowy.data.repositories + +import androidx.room.withTransaction +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsAuthorized +import io.github.wulkanowy.data.db.entities.StudentName +import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.exceptions.NoCurrentStudentException +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.mappers.mapToPojo +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.security.Scrambler +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class StudentRepository @Inject constructor( + private val dispatchers: DispatchersProvider, + private val studentDb: StudentDao, + private val semesterDb: SemesterDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val appDatabase: AppDatabase, + private val scrambler: Scrambler, +) { + + suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false + + suspend fun getStudentsApi( + pin: String, + symbol: String, + token: String + ): RegisterUser = wulkanowySdkFactory.create() + .getStudentsFromHebe(token, pin, symbol, "") + .mapToPojo(null) + .also { it.logErrors() } + + suspend fun getUserSubjectsFromScrapper( + email: String, + password: String, + scrapperBaseUrl: String, + domainSuffix: String, + symbol: String + ): RegisterUser = wulkanowySdkFactory.create() + .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol) + .mapToPojo(password) + .also { it.logErrors() } + + suspend fun getStudentsHybrid( + email: String, + password: String, + scrapperBaseUrl: String, + symbol: String + ): RegisterUser = wulkanowySdkFactory.create() + .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) + .mapToPojo(password) + .also { it.logErrors() } + + suspend fun getSavedStudents(decryptPass: Boolean = true): List { + return studentDb.loadStudentsWithSemesters().map { (student, semesters) -> + StudentWithSemesters( + student = student.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { + student.password = withContext(dispatchers.io) { + scrambler.decrypt(student.password) + } + } + }, + semesters = semesters, + ) + } + } + + suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? = + studentDb.loadStudentWithSemestersById(id).let { res -> + StudentWithSemesters( + student = res.keys.firstOrNull() ?: return null, + semesters = res.values.first(), + ) + }.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { + student.password = withContext(dispatchers.io) { + scrambler.decrypt(student.password) + } + } + } + + suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { + val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() + + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { + student.password = withContext(dispatchers.io) { + scrambler.decrypt(student.password) + } + } + return student + } + + suspend fun updateCurrentStudentAuthStatus() { + Timber.i("Check isAuthorized: started") + val student = getCurrentStudent() + if (student.isAuthorized) { + Timber.i("Check isAuthorized: already authorized") + return + } + + val initializedSdk = wulkanowySdkFactory.create(student) + val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Check isAuthorized: error occurred") } + .getOrNull() + + if (newCurrentStudent == null) { + Timber.d("Check isAuthorized: current user is null") + return + } + + val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) + if (currentStudentSemesters.isEmpty()) { + Timber.d("Check isAuthorized: apply empty semesters workaround") + semesterDb.insertSemesters( + items = newCurrentStudent.semesters.mapToEntities(student.studentId), + ) + } + + if (!newCurrentStudent.isAuthorized) { + Timber.i("Check isAuthorized: authorization required") + throw NoAuthorizationException() + } + + val studentIsAuthorized = StudentIsAuthorized( + id = student.id, + isAuthorized = true + ) + + Timber.i("Check isAuthorized: already authorized, update local status") + studentDb.update(studentIsAuthorized) + } + + suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { + val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException() + + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { + student.password = withContext(dispatchers.io) { + scrambler.decrypt(student.password) + } + } + return student + } + + suspend fun saveStudents(studentsWithSemesters: List) { + val semesters = studentsWithSemesters.flatMap { it.semesters } + val students = studentsWithSemesters.map { it.student } + .map { + it.apply { + if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) { + password = withContext(dispatchers.io) { + scrambler.encrypt(password) + } + } + } + } + .mapIndexed { index, student -> + if (index == 0) { + student.copy(isCurrent = true).apply { avatarColor = student.avatarColor } + } else student + } + + appDatabase.withTransaction { + studentDb.resetCurrent() + semesterDb.insertSemesters(semesters) + studentDb.insertAll(students) + } + } + + suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { + studentDb.switchCurrent(studentWithSemesters.student.id) + } + + suspend fun logoutStudent(student: Student) = studentDb.delete(student) + + suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) = + studentDb.update(studentNickAndAvatar) + + suspend fun isOneUniqueStudent() = getSavedStudents(false) + .distinctBy { it.student.studentName }.size == 1 + + suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = + wulkanowySdkFactory.create(student, semester) + .authorizePermission(pesel) + + suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) { + val wulkanowySdk = wulkanowySdkFactory.create(student, semester) + val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") } + .getOrNull() ?: return + + val studentName = StudentName( + studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}" + ).apply { id = student.id } + + studentDb.update(studentName) + semesterDb.removeOldAndSaveNew( + oldItems = semesterDb.loadAll(student.studentId, semester.classId), + newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) + ) + } + + suspend fun deleteStudentsAssociatedWithAccount(student: Student) { + studentDb.deleteByEmailAndUserName(student.email, student.userName) + } + + suspend fun clearAll() { + withContext(dispatchers.io) { + scrambler.clearKeyPair() + appDatabase.clearAllTables() + } + } + + private fun RegisterUser.logErrors() { + val symbolsErrors = symbols.filter { it.error != null } + .map { it.error } + val unitsErrors = symbols.flatMap { it.schools } + .filter { it.error != null } + .map { it.error } + + (symbolsErrors + unitsErrors).forEach { error -> + Timber.e(error, "Error occurred while fetching students") + } + } +} + +class NoAuthorizationException : Exception() diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt new file mode 100644 index 00000000..573c7c14 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.SubjectDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubjectRepository @Inject constructor( + private val subjectDao: SubjectDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "subjects" + + fun getSubjects( + student: Student, + semester: Semester, + forceRefresh: Boolean = false, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getSubjects() + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + subjectDao.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt new file mode 100644 index 00000000..a5a6e3f9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.TeacherDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.sync.Mutex +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TeacherRepository @Inject constructor( + private val teacherDb: TeacherDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val refreshHelper: AutoRefreshHelper, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "teachers" + + fun getTeachers( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) + it.isEmpty() || forceRefresh || isExpired + }, + query = { teacherDb.loadAll(semester.studentId, semester.classId) }, + fetch = { + wulkanowySdkFactory.create(student, semester) + .getTeachers() + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + teacherDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt new file mode 100644 index 00000000..60c562e1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -0,0 +1,203 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao +import io.github.wulkanowy.data.db.dao.TimetableDao +import io.github.wulkanowy.data.db.dao.TimetableHeaderDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.data.pojos.TimetableFull +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider +import io.github.wulkanowy.utils.AppWidgetUpdater +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.sync.Mutex +import java.time.Instant +import java.time.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + + +@Singleton +class TimetableRepository @Inject constructor( + private val timetableDb: TimetableDao, + private val timetableAdditionalDb: TimetableAdditionalDao, + private val timetableHeaderDb: TimetableHeaderDao, + private val wulkanowySdkFactory: WulkanowySdkFactory, + private val schedulerHelper: TimetableNotificationSchedulerHelper, + private val refreshHelper: AutoRefreshHelper, + private val appWidgetUpdater: AppWidgetUpdater, +) { + + private val saveFetchResultMutex = Mutex() + + private val cacheKey = "timetable" + + enum class TimetableType { + NORMAL, ADDITIONAL + } + + fun getTimetable( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + forceRefresh: Boolean, + refreshAdditional: Boolean = false, + notify: Boolean = false, + timetableType: TimetableType = TimetableType.NORMAL, + isFromAppWidget: Boolean = false + ) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { + when (timetableType) { + TimetableType.NORMAL -> it.lessons.isEmpty() + TimetableType.ADDITIONAL -> it.additional.isEmpty() + } + }, + shouldFetch = { (timetable, additional, headers) -> + val refreshKey = getRefreshKey(cacheKey, semester, start, end) + val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) + val isRefreshAdditional = additional.isEmpty() && refreshAdditional + + val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty() + + isNoData || forceRefresh || isExpired + }, + query = { getFullTimetableFromDatabase(student, semester, start, end) }, + fetch = { + val timetableFull = wulkanowySdkFactory.create(student, semester) + .getTimetable(start.monday, end.sunday) + + timetableFull.mapToEntities(semester) + }, + saveFetchResult = { timetableOld, timetableNew -> + refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify) + refreshAdditional(timetableOld.additional, timetableNew.additional) + refreshDayHeaders(timetableOld.headers, timetableNew.headers) + + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + if (!isFromAppWidget) { + appWidgetUpdater.updateAllAppWidgetsByProvider(TimetableWidgetProvider::class) + } + }, + filterResult = { (timetable, additional, headers) -> + TimetableFull( + lessons = timetable.filter { it.date in start..end }, + additional = additional.filter { it.date in start..end }, + headers = headers.filter { it.date in start..end } + ) + } + ) + + private fun getFullTimetableFromDatabase( + student: Student, + semester: Semester, + start: LocalDate, + end: LocalDate, + ): Flow { + val timetableFlow = timetableDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start.monday, + end = end.sunday + ) + val headersFlow = timetableHeaderDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start.monday, + end = end.sunday + ) + val additionalFlow = timetableAdditionalDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start.monday, + end = end.sunday + ) + return combine(timetableFlow, headersFlow, additionalFlow) { lessons, headers, additional -> + schedulerHelper.scheduleNotifications(lessons, student) + + TimetableFull( + lessons = lessons, + headers = headers, + additional = additional + ) + } + } + + suspend fun getTimetableFromDatabase( + semester: Semester, + start: LocalDate, + end: LocalDate + ): List { + return timetableDb.load(semester.diaryId, semester.studentId, start, end) + } + + suspend fun updateTimetable(timetable: List) { + return timetableDb.updateAll(timetable) + } + + private suspend fun refreshTimetable( + student: Student, + lessonsOld: List, + lessonsNew: List, + notify: Boolean + ) { + val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew + val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new -> + new.apply { if (notify) isNotified = false } + } + + timetableDb.removeOldAndSaveNew( + oldItems = lessonsToRemove, + newItems = lessonsToAdd, + ) + + schedulerHelper.cancelScheduled(lessonsToRemove, student) + schedulerHelper.scheduleNotifications(lessonsToAdd, student) + } + + private suspend fun refreshAdditional( + old: List, + new: List + ) { + val oldFiltered = old.filter { !it.isAddedByUser } + timetableAdditionalDb.removeOldAndSaveNew( + oldItems = oldFiltered uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + } + + private suspend fun refreshDayHeaders(old: List, new: List) { + timetableHeaderDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) + } + + fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant { + val refreshKey = getRefreshKey(cacheKey, semester, start, end) + return refreshHelper.getLastRefreshTimestamp(refreshKey) + } + + suspend fun saveAdditionalList(additionalList: List) = + timetableAdditionalDb.insertAll(additionalList) + + suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) = + if (deleteSeries) { + timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!) + } else { + timetableAdditionalDb.deleteAll(listOf(additional)) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/WulkanowyRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/WulkanowyRepository.kt new file mode 100644 index 00000000..5b563b35 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/WulkanowyRepository.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.api.models.Mapping +import io.github.wulkanowy.data.api.services.WulkanowyService +import io.github.wulkanowy.data.db.dao.AdminMessageDao +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.networkBoundResource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.sync.Mutex +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WulkanowyRepository @Inject constructor( + private val wulkanowyService: WulkanowyService, + private val adminMessageDao: AdminMessageDao, + private val preferencesRepository: PreferencesRepository, +) { + + private val saveFetchResultMutex = Mutex() + + fun getAdminMessages(): Flow>> = + networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { false }, + query = { adminMessageDao.loadAll() }, + fetch = { wulkanowyService.getAdminMessages() }, + shouldFetch = { true }, + saveFetchResult = { oldItems, newItems -> + adminMessageDao.removeOldAndSaveNew(oldItems, newItems) + }, + ) + .filterNot { it is Resource.Intermediate } + + suspend fun getMapping(): Mapping? { + var savedMapping = preferencesRepository.mapping + + if (savedMapping == null) { + fetchMapping() + savedMapping = preferencesRepository.mapping + } + + return savedMapping + } + + suspend fun fetchMapping() { + runCatching { wulkanowyService.getMapping() } + .onFailure { Timber.e(it) } + .onSuccess { preferencesRepository.mapping = it } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt deleted file mode 100644 index fa752ed2..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.wulkanowy.data.repositories.appcreator - -import android.content.res.AssetManager -import com.google.gson.Gson -import io.github.wulkanowy.data.pojos.AppCreator -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { - fun getAppCreators(): Single> { - return Single.fromCallable> { - Gson().fromJson( - assets.open("contributors.json").bufferedReader().use { it.readText() }, - Array::class.java - ).toList() - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt deleted file mode 100644 index 0f587376..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.dao.AttendanceDao -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) { - - fun saveAttendance(attendance: List) { - attendanceDb.insertAll(attendance) - } - - fun deleteAttendance(attendance: List) { - attendanceDb.deleteAll(attendance) - } - - fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return attendanceDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt deleted file mode 100644 index f64d920d..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Absent -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime -import org.threeten.bp.LocalTime -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceRemote @Inject constructor(private val sdk: Sdk) { - - fun getAttendance(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getAttendance(startDate, endDate, semester.semesterId) - .map { attendance -> - attendance.map { - Attendance( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - timeId = it.timeId, - number = it.number, - subject = it.subject, - name = it.name, - presence = it.presence, - absence = it.absence, - exemption = it.exemption, - lateness = it.lateness, - excused = it.excused, - deleted = it.deleted, - excusable = it.excusable, - excuseStatus = it.excuseStatus?.name - ) - } - } - } - - fun excuseAbsence(student: Student, semester: Semester, absenceList: List, reason: String?): Single { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance -> - Absent( - date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)), - timeId = attendance.timeId - ) - }, reason) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt deleted file mode 100644 index 22fe7fa5..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: AttendanceLocal, - private val remote: AttendanceRemote -) { - - fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean): Single> { - return local.getAttendance(semester, start.monday, end.friday).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getAttendance(student, semester, start.monday, end.friday) - else Single.error(UnknownHostException()) - }.flatMap { newAttendance -> - local.getAttendance(semester, start.monday, end.friday) - .toSingle(emptyList()) - .doOnSuccess { oldAttendance -> - local.deleteAttendance(oldAttendance.uniqueSubtract(newAttendance)) - local.saveAttendance(newAttendance.uniqueSubtract(oldAttendance)) - } - }.flatMap { - local.getAttendance(semester, start.monday, end.friday) - .toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } - } - - fun excuseForAbsence(student: Student, semester: Semester, attendanceList: List, reason: String? = null): Single { - return remote.excuseAbsence(student, semester, attendanceList, reason) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt deleted file mode 100644 index 2e9a1006..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryLocal @Inject constructor(private val attendanceDb: AttendanceSummaryDao) { - - fun saveAttendanceSummary(attendance: List) { - attendanceDb.insertAll(attendance) - } - - fun deleteAttendanceSummary(attendance: List) { - attendanceDb.deleteAll(attendance) - } - - fun getAttendanceSummary(semester: Semester, subjectId: Int): Maybe> { - return attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt deleted file mode 100644 index 8fef5c39..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryRemote @Inject constructor(private val sdk: Sdk) { - - fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getAttendanceSummary(subjectId) - .map { attendance -> - attendance.map { - AttendanceSummary( - studentId = semester.studentId, - diaryId = semester.diaryId, - subjectId = subjectId, - month = it.month, - presence = it.presence, - absence = it.absence, - absenceExcused = it.absenceExcused, - absenceForSchoolReasons = it.absenceForSchoolReasons, - lateness = it.lateness, - latenessExcused = it.latenessExcused, - exemption = it.exemption - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt deleted file mode 100644 index 5f0b2b34..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: AttendanceSummaryLocal, - private val remote: AttendanceSummaryRemote -) { - - fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean = false): Single> { - return local.getAttendanceSummary(semester, subjectId).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getAttendanceSummary(student, semester, subjectId) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteAttendanceSummary(old.uniqueSubtract(new)) - local.saveAttendanceSummary(new.uniqueSubtract(old)) - } - }.flatMap { local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) }) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt deleted file mode 100644 index 9b275908..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import io.github.wulkanowy.data.db.dao.CompletedLessonsDao -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CompletedLessonsLocal @Inject constructor(private val completedLessonsDb: CompletedLessonsDao) { - - fun saveCompletedLessons(completedLessons: List) { - completedLessonsDb.insertAll(completedLessons) - } - - fun deleteCompleteLessons(completedLessons: List) { - completedLessonsDb.deleteAll(completedLessons) - } - - fun getCompletedLessons(semester: Semester, start: LocalDate, end: LocalDate): Maybe> { - return completedLessonsDb.loadAll(semester.diaryId, semester.studentId, start, end).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt deleted file mode 100644 index bb115111..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CompletedLessonsRemote @Inject constructor(private val sdk: Sdk) { - - fun getCompletedLessons(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getCompletedLessons(startDate, endDate) - .map { lessons -> - lessons.map { - it.absence - CompletedLesson( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - number = it.number, - subject = it.subject, - topic = it.topic, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - substitution = it.substitution, - absence = it.absence, - resources = it.resources - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt deleted file mode 100644 index f31e3055..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CompletedLessonsRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: CompletedLessonsLocal, - private val remote: CompletedLessonsRemote -) { - - fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return local.getCompletedLessons(semester, start.monday, end.friday).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getCompletedLessons(student, semester, start.monday, end.friday) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getCompletedLessons(semester, start.monday, end.friday) - .toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteCompleteLessons(old.uniqueSubtract(new)) - local.saveCompletedLessons(new.uniqueSubtract(old)) - } - }.flatMap { - local.getCompletedLessons(semester, start.monday, end.friday) - .toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt deleted file mode 100644 index 0f484d32..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import io.github.wulkanowy.data.db.dao.ExamDao -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExamLocal @Inject constructor(private val examDb: ExamDao) { - - fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - .filter { !it.isEmpty() } - } - - fun saveExams(exams: List) { - examDb.insertAll(exams) - } - - fun deleteExams(exams: List) { - examDb.deleteAll(exams) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt deleted file mode 100644 index fb105cee..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExamRemote @Inject constructor(private val sdk: Sdk) { - - fun getExams(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getExams(startDate, endDate, semester.semesterId) - .map { exams -> - exams.map { - Exam( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - entryDate = it.entryDate, - subject = it.subject, - group = it.group, - type = it.type, - description = it.description, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt deleted file mode 100644 index 452a1551..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExamRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: ExamLocal, - private val remote: ExamRemote -) { - - fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return local.getExams(semester, start.monday, end.friday).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getExams(student, semester, start.monday, end.friday) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getExams(semester, start.monday, end.friday) - .toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteExams(old.uniqueSubtract(new)) - local.saveExams(new.uniqueSubtract(old)) - } - }.flatMap { - local.getExams(semester, start.monday, end.friday) - .toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt deleted file mode 100644 index 4983a474..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import io.github.wulkanowy.data.db.dao.GradeDao -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeLocal @Inject constructor(private val gradeDb: GradeDao) { - - fun saveGrades(grades: List) { - gradeDb.insertAll(grades) - } - - fun deleteGrades(grades: List) { - gradeDb.deleteAll(grades) - } - - fun updateGrades(grades: List) { - gradeDb.updateAll(grades) - } - - fun getGrades(semester: Semester): Maybe> { - return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt deleted file mode 100644 index 6f19095b..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeRemote @Inject constructor(private val sdk: Sdk) { - - fun getGrades(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGrades(semester.semesterId) - .map { grades -> - grades.map { - Grade( - studentId = semester.studentId, - semesterId = semester.semesterId, - subject = it.subject, - entry = it.entry, - value = it.value, - modifier = it.modifier, - comment = it.comment, - color = it.color, - gradeSymbol = it.symbol, - description = it.description, - weight = it.weight, - weightValue = it.weightValue, - date = it.date, - teacher = it.teacher - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt deleted file mode 100644 index 351ebc39..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Completable -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: GradeLocal, - private val remote: GradeRemote -) { - - fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single> { - return local.getGrades(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGrades(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGrades(semester).toSingle(emptyList()) - .doOnSuccess { old -> - val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate() - local.deleteGrades(old.uniqueSubtract(new)) - local.saveGrades(new.uniqueSubtract(old) - .onEach { - if (it.date >= notifyBreakDate) it.apply { - isRead = false - if (notify) isNotified = false - } - }) - } - }.flatMap { local.getGrades(semester).toSingle(emptyList()) }) - } - - fun getUnreadGrades(semester: Semester): Single> { - return local.getGrades(semester).map { it.filter { grade -> !grade.isRead } }.toSingle(emptyList()) - } - - fun getNotNotifiedGrades(semester: Semester): Single> { - return local.getGrades(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) - } - - fun updateGrade(grade: Grade): Completable { - return Completable.fromCallable { local.updateGrades(listOf(grade)) } - } - - fun updateGrades(grades: List): Completable { - return Completable.fromCallable { local.updateGrades(grades) } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt deleted file mode 100644 index e74641d3..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -import io.github.wulkanowy.data.db.dao.GradeSummaryDao -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) { - - fun saveGradesSummary(gradesSummary: List) { - gradeSummaryDb.insertAll(gradesSummary) - } - - fun deleteGradesSummary(gradesSummary: List) { - gradeSummaryDb.deleteAll(gradesSummary) - } - - fun getGradesSummary(semester: Semester): Maybe> { - return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt deleted file mode 100644 index 1b09974d..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRemote.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeSummaryRemote @Inject constructor(private val sdk: Sdk) { - - fun getGradeSummary(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesSummary(semester.semesterId) - .map { gradesSummary -> - gradesSummary.map { - GradeSummary( - semesterId = semester.semesterId, - studentId = semester.studentId, - position = 0, - subject = it.name, - predictedGrade = it.predicted, - finalGrade = it.final, - pointsSum = it.pointsSum, - proposedPoints = it.proposedPoints, - finalPoints = it.finalPoints, - average = it.average - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt deleted file mode 100644 index f1d7a6c1..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradessummary/GradeSummaryRepository.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradessummary - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeSummaryRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: GradeSummaryLocal, - private val remote: GradeSummaryRemote -) { - - fun getGradesSummary(student: Student, semester: Semester, forceRefresh: Boolean = false): Single> { - return local.getGradesSummary(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGradeSummary(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGradesSummary(semester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteGradesSummary(old.uniqueSubtract(new)) - local.saveGradesSummary(new.uniqueSubtract(old)) - } - }.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) }) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt deleted file mode 100644 index 7994bd75..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeStatisticsDao -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeStatisticsLocal @Inject constructor( - private val gradeStatisticsDb: GradeStatisticsDao, - private val gradePointsStatisticsDb: GradePointsStatisticsDao -) { - - fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe> { - return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).filter { it.isNotEmpty() } - } - - fun getGradesPointsStatistics(semester: Semester): Maybe> { - return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } - } - - fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe> { - return when (subjectName) { - "Wszystkie" -> gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list -> - list.groupBy { it.grade }.map { - GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, - it.value.fold(0) { acc, e -> acc + e.amount }, false) - } + list - } - else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester) - }.filter { it.isNotEmpty() } - } - - fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe> { - return when (subjectName) { - "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) - else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName) - }.filter { it.isNotEmpty() } - } - - fun saveGradesStatistics(gradesStatistics: List) { - gradeStatisticsDb.insertAll(gradesStatistics) - } - - fun saveGradesPointsStatistics(gradePointsStatistics: List) { - gradePointsStatisticsDb.insertAll(gradePointsStatistics) - } - - fun deleteGradesStatistics(gradesStatistics: List) { - gradeStatisticsDb.deleteAll(gradesStatistics) - } - - fun deleteGradesPointsStatistics(gradesPointsStatistics: List) { - gradePointsStatisticsDb.deleteAll(gradesPointsStatistics) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt deleted file mode 100644 index 99e0cb98..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeStatisticsRemote @Inject constructor(private val sdk: Sdk) { - - fun getGradeStatistics(student: Student, semester: Semester, isSemester: Boolean): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).let { - if (isSemester) it.getGradesAnnualStatistics(semester.semesterId) - else it.getGradesPartialStatistics(semester.semesterId) - }.map { gradeStatistics -> - gradeStatistics.map { - GradeStatistics( - semesterId = semester.semesterId, - studentId = semester.studentId, - subject = it.subject, - grade = it.gradeValue, - amount = it.amount, - semester = isSemester - ) - } - } - } - - fun getGradePointsStatistics(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesPointsStatistics(semester.semesterId) - .map { gradePointsStatistics -> - gradePointsStatistics.map { - GradePointsStatistics( - semesterId = semester.semesterId, - studentId = semester.studentId, - subject = it.subject, - others = it.others, - student = it.student - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt deleted file mode 100644 index 8d96d2f5..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.GradeStatisticsItem -import io.github.wulkanowy.ui.modules.grade.statistics.ViewType -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeStatisticsRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: GradeStatisticsLocal, - private val remote: GradeStatisticsRemote -) { - - fun getGradesStatistics(student: Student, semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single> { - return local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGradeStatistics(student, semester, isSemester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGradesStatistics(semester, isSemester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteGradesStatistics(old.uniqueSubtract(new)) - local.saveGradesStatistics(new.uniqueSubtract(old)) - } - }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.toSingle(emptyList()) }) - } - - fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean): Single> { - return local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGradePointsStatistics(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getGradesPointsStatistics(semester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteGradesPointsStatistics(old.uniqueSubtract(new)) - local.saveGradesPointsStatistics(new.uniqueSubtract(old)) - } - }.flatMap { local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.toSingle(emptyList()) }) - } - - private fun List.mapToStatisticItems(): List { - return groupBy { it.subject }.map { - GradeStatisticsItem( - type = ViewType.PARTIAL, - partial = it.value - .sortedByDescending { item -> item.grade } - .filter { item -> item.amount != 0 }, - points = null - ) - } - } - - private fun List.mapToStatisticsItem(): List { - return map { - GradeStatisticsItem( - type = ViewType.POINTS, - partial = emptyList(), - points = it - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt deleted file mode 100644 index fdae4518..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.data.repositories.homework - -import io.github.wulkanowy.data.db.dao.HomeworkDao -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) { - - fun saveHomework(homework: List) { - homeworkDb.insertAll(homework) - } - - fun deleteHomework(homework: List) { - homeworkDb.deleteAll(homework) - } - - fun updateHomework(homework: List) { - homeworkDb.updateAll(homework) - } - - fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate) - .filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt deleted file mode 100644 index 20ffee99..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.homework - -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeworkRemote @Inject constructor(private val sdk: Sdk) { - - fun getHomework(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getHomework(startDate, endDate) - .map { homework -> - homework.map { - Homework( - semesterId = semester.semesterId, - studentId = semester.studentId, - date = it.date, - entryDate = it.entryDate, - subject = it.subject, - content = it.content, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - attachments = it.attachments.map { attachment -> attachment.url to attachment.name } - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt deleted file mode 100644 index 4454fd88..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.wulkanowy.data.repositories.homework - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Completable -import io.reactivex.Single -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeworkRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: HomeworkLocal, - private val remote: HomeworkRemote -) { - - fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) -> - local.getHomework(semester, monday, friday).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getHomework(student, semester, monday, friday) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getHomework(semester, monday, friday).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteHomework(old.uniqueSubtract(new)) - local.saveHomework(new.uniqueSubtract(old)) - } - }.flatMap { local.getHomework(semester, monday, friday).toSingle(emptyList()) }) - } - } - - fun toggleDone(homework: Homework): Completable { - return Completable.fromCallable { - local.updateHomework(listOf(homework.apply { - isDone = !isDone - })) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt deleted file mode 100644 index be6ad2f4..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.wulkanowy.data.repositories.logger - -import android.content.Context -import io.reactivex.Single -import java.io.File -import java.io.FileNotFoundException -import javax.inject.Inject - -class LoggerRepository @Inject constructor(private val context: Context) { - - fun getLastLogLines(): Single> { - return getLastModified() - .map { it.readText() } - .map { it.split("\n") } - } - - fun getLogFiles(): Single> { - return Single.fromCallable { - File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter { - it.name.endsWith(".log") - } - } - } - - private fun getLastModified(): Single { - return Single.fromCallable { - var lastModifiedTime = Long.MIN_VALUE - var chosenFile: File? = null - File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file -> - if (file.lastModified() > lastModifiedTime) { - lastModifiedTime = file.lastModified() - chosenFile = file - } - } - if (chosenFile == null) throw FileNotFoundException("Log file not found") - chosenFile - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt deleted file mode 100644 index 0f4f79c8..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.data.db.dao.LuckyNumberDao -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumberDao) { - - fun saveLuckyNumber(luckyNumber: LuckyNumber) { - luckyNumberDb.insertAll(listOf(luckyNumber)) - } - - fun updateLuckyNumber(luckyNumber: LuckyNumber) { - luckyNumberDb.updateAll(listOf(luckyNumber)) - } - - fun deleteLuckyNumber(luckyNumber: LuckyNumber) { - luckyNumberDb.deleteAll(listOf(luckyNumber)) - } - - fun getLuckyNumber(student: Student, date: LocalDate): Maybe { - return luckyNumberDb.load(student.studentId, date) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt deleted file mode 100644 index 0c71897a..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberRemote @Inject constructor(private val sdk: Sdk) { - - fun getLuckyNumber(student: Student): Maybe { - return sdk.init(student).getLuckyNumber(student.schoolShortName).map { - LuckyNumber( - studentId = student.studentId, - date = LocalDate.now(), - luckyNumber = it - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt deleted file mode 100644 index 374b9a29..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Completable -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: LuckyNumberLocal, - private val remote: LuckyNumberRemote -) { - - fun getLuckyNumber(student: Student, forceRefresh: Boolean = false, notify: Boolean = false): Maybe { - return local.getLuckyNumber(student, LocalDate.now()).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMapMaybe { - if (it) remote.getLuckyNumber(student) - else Maybe.error(UnknownHostException()) - }.flatMap { new -> - local.getLuckyNumber(student, LocalDate.now()) - .doOnSuccess { old -> - if (new != old) { - local.deleteLuckyNumber(old) - local.saveLuckyNumber(new.apply { - if (notify) isNotified = false - }) - } - } - .doOnComplete { - local.saveLuckyNumber(new.apply { - if (notify) isNotified = false - }) - } - }.flatMap({ local.getLuckyNumber(student, LocalDate.now()) }, { Maybe.error(it) }, - { local.getLuckyNumber(student, LocalDate.now()) }) - ) - } - - fun getNotNotifiedLuckyNumber(student: Student): Maybe { - return local.getLuckyNumber(student, LocalDate.now()).filter { !it.isNotified } - } - - fun updateLuckyNumber(luckyNumber: LuckyNumber): Completable { - return Completable.fromCallable { local.updateLuckyNumber(luckyNumber) } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt deleted file mode 100644 index 06f5a1e0..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -enum class MessageFolder(val id: Int = 1) { - RECEIVED(1), - SENT(2), - TRASHED(3) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt deleted file mode 100644 index 53cf0a98..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import io.github.wulkanowy.data.db.dao.MessageAttachmentDao -import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED -import io.reactivex.Maybe -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MessageLocal @Inject constructor( - private val messagesDb: MessagesDao, - private val messageAttachmentDao: MessageAttachmentDao -) { - - fun saveMessages(messages: List) { - messagesDb.insertAll(messages) - } - - fun updateMessages(messages: List) { - messagesDb.updateAll(messages) - } - - fun deleteMessages(messages: List) { - messagesDb.deleteAll(messages) - } - - fun getMessageWithAttachment(student: Student, message: Message): Single { - return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) - } - - fun saveMessageAttachments(attachments: List) { - messageAttachmentDao.insertAttachments(attachments) - } - - fun getMessages(student: Student, folder: MessageFolder): Maybe> { - return when (folder) { - TRASHED -> messagesDb.loadDeleted(student.id.toInt()) - else -> messagesDb.loadAll(student.id.toInt(), folder.id) - }.filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt deleted file mode 100644 index 1808c048..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Folder -import io.github.wulkanowy.sdk.pojo.SentMessage -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDateTime.now -import javax.inject.Inject -import javax.inject.Singleton -import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient - -@Singleton -class MessageRemote @Inject constructor(private val sdk: Sdk) { - - fun getMessages(student: Student, semester: Semester, folder: MessageFolder): Single> { - return sdk.init(student).getMessages(Folder.valueOf(folder.name), semester.start.atStartOfDay(), semester.end.atStartOfDay()).map { messages -> - messages.map { - Message( - studentId = student.id.toInt(), - realId = it.id ?: 0, - messageId = it.messageId ?: 0, - sender = it.sender.orEmpty(), - senderId = it.senderId ?: 0, - recipient = it.recipient.orEmpty(), - subject = it.subject.trim(), - date = it.date ?: now(), - content = it.content.orEmpty(), - folderId = it.folderId, - unread = it.unread ?: false, - unreadBy = it.unreadBy ?: 0, - readBy = it.readBy ?: 0, - removed = it.removed, - hasAttachments = it.hasAttachments - ) - } - } - } - - fun getMessagesContentDetails(student: Student, message: Message, markAsRead: Boolean = false): Single>> { - return sdk.init(student).getMessageDetails(message.messageId, message.folderId, markAsRead, message.realId).map { details -> - details.content to details.attachments.map { - MessageAttachment( - realId = it.id, - messageId = it.messageId, - oneDriveId = it.oneDriveId, - url = it.url, - filename = it.filename - ) - } - } - } - - fun sendMessage(student: Student, subject: String, content: String, recipients: List): Single { - return sdk.init(student).sendMessage( - subject = subject, - content = content, - recipients = recipients.map { - SdkRecipient( - id = it.realId, - name = it.realName, - loginId = it.loginId, - reportingUnitId = it.unitId, - role = it.role, - hash = it.hash, - shortName = it.name - ) - } - ) - } - - fun deleteMessage(student: Student, message: Message): Single { - return sdk.init(student).deleteMessages(listOf(Pair(message.realId, message.folderId))) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt deleted file mode 100644 index 2d2c0430..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED -import io.github.wulkanowy.sdk.pojo.SentMessage -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Completable -import io.reactivex.Single -import timber.log.Timber -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MessageRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: MessageLocal, - private val remote: MessageRemote -) { - - fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single> { - return local.getMessages(student, folder).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getMessages(student, semester, folder) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getMessages(student, folder).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteMessages(old.uniqueSubtract(new)) - local.saveMessages(new.uniqueSubtract(old) - .onEach { - it.isNotified = !notify - }) - } - }.flatMap { local.getMessages(student, folder).toSingle(emptyList()) } - ) - } - - fun getMessage(student: Student, message: Message, markAsRead: Boolean = false): Single { - return local.getMessageWithAttachment(student, message) - .filter { - it.message.content.isNotEmpty().also { status -> - Timber.d("Message content in db empty: ${!status}") - } && !it.message.unread - } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) local.getMessageWithAttachment(student, message) - else Single.error(UnknownHostException()) - } - .flatMap { dbMessage -> - remote.getMessagesContentDetails(student, dbMessage.message, markAsRead).doOnSuccess { (downloadedMessage, attachments) -> - local.updateMessages(listOf(dbMessage.message.copy(unread = !markAsRead).apply { - id = dbMessage.message.id - content = content.ifBlank { downloadedMessage } - })) - local.saveMessageAttachments(attachments) - Timber.d("Message ${message.messageId} with blank content: ${dbMessage.message.content.isBlank()}, marked as read") - } - }.flatMap { - local.getMessageWithAttachment(student, message) - } - ) - } - - fun getNotNotifiedMessages(student: Student): Single> { - return local.getMessages(student, RECEIVED) - .map { it.filter { message -> !message.isNotified && message.unread } } - .toSingle(emptyList()) - } - - fun updateMessages(messages: List): Completable { - return Completable.fromCallable { local.updateMessages(messages) } - } - - fun sendMessage(student: Student, subject: String, content: String, recipients: List): Single { - return ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.sendMessage(student, subject, content, recipients) - else Single.error(UnknownHostException()) - } - } - - fun deleteMessage(student: Student, message: Message): Single { - return ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.deleteMessage(student, message) - else Single.error(UnknownHostException()) - } - .doOnSuccess { - if (!message.removed) local.updateMessages(listOf(message.copy(removed = true).apply { - id = message.id - content = message.content - })) - else local.deleteMessages(listOf(message)) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt deleted file mode 100644 index 473ffa7f..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.dao.MobileDeviceDao -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceLocal @Inject constructor(private val mobileDb: MobileDeviceDao) { - - fun saveDevices(devices: List) { - mobileDb.insertAll(devices) - } - - fun deleteDevices(devices: List) { - mobileDb.deleteAll(devices) - } - - fun getDevices(semester: Semester): Maybe> { - return mobileDb.loadAll(semester.studentId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt deleted file mode 100644 index 800f9597..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceRemote @Inject constructor(private val sdk: Sdk) { - - fun getDevices(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getRegisteredDevices() - .map { devices -> - devices.map { - MobileDevice( - studentId = semester.studentId, - date = it.createDate, - deviceId = it.id, - name = it.name - ) - } - } - } - - fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice): Single { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .unregisterDevice(device.deviceId) - } - - fun getToken(student: Student, semester: Semester): Single { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getToken() - .map { - MobileDeviceToken( - token = it.token, - symbol = it.symbol, - pin = it.pin, - qr = it.qrCodeImage - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt deleted file mode 100644 index 545846e8..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: MobileDeviceLocal, - private val remote: MobileDeviceRemote -) { - - fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean = false): Single> { - return local.getDevices(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getDevices(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getDevices(semester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteDevices(old uniqueSubtract new) - local.saveDevices(new uniqueSubtract old) - } - } - ).flatMap { local.getDevices(semester).toSingle(emptyList()) } - } - - fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice): Single { - return ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.unregisterDevice(student, semester, device) - else Single.error(UnknownHostException()) - } - } - - fun getToken(student: Student, semester: Semester): Single { - return ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getToken(student, semester) - else Single.error(UnknownHostException()) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt deleted file mode 100644 index 784e61f0..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.note - -import io.github.wulkanowy.data.db.dao.NoteDao -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NoteLocal @Inject constructor(private val noteDb: NoteDao) { - - fun saveNotes(notes: List) { - noteDb.insertAll(notes) - } - - fun updateNotes(notes: List) { - noteDb.updateAll(notes) - } - - fun deleteNotes(notes: List) { - noteDb.deleteAll(notes) - } - - fun getNotes(student: Student): Maybe> { - return noteDb.loadAll(student.studentId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt deleted file mode 100644 index 2c62b608..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.wulkanowy.data.repositories.note - -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NoteRemote @Inject constructor(private val sdk: Sdk) { - - fun getNotes(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getNotes(semester.semesterId) - .map { notes -> - notes.map { - Note( - studentId = semester.studentId, - date = it.date, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - category = it.category, - categoryType = it.categoryType.id, - isPointsShow = it.showPoints, - points = it.points, - content = it.content - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt deleted file mode 100644 index e155e2ba..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.wulkanowy.data.repositories.note - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Completable -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NoteRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: NoteLocal, - private val remote: NoteRemote -) { - - fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single> { - return local.getNotes(student).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getNotes(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getNotes(student).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteNotes(old.uniqueSubtract(new)) - local.saveNotes(new.uniqueSubtract(old) - .onEach { - if (it.date >= student.registrationDate.toLocalDate()) it.apply { - isRead = false - if (notify) isNotified = false - } - }) - } - }.flatMap { local.getNotes(student).toSingle(emptyList()) }) - } - - fun getNotNotifiedNotes(student: Student): Single> { - return local.getNotes(student).map { it.filter { note -> !note.isNotified } }.toSingle(emptyList()) - } - - fun updateNote(note: Note): Completable { - return Completable.fromCallable { local.updateNotes(listOf(note)) } - } - - fun updateNotes(notes: List): Completable { - return Completable.fromCallable { local.updateNotes(notes) } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt deleted file mode 100644 index 523caf6c..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.wulkanowy.data.repositories.preferences - -import android.content.Context -import android.content.SharedPreferences -import io.github.wulkanowy.R -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class PreferencesRepository @Inject constructor( - private val sharedPref: SharedPreferences, - val context: Context -) { - val startMenuIndex: Int - get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt() - - val isShowPresent: Boolean - get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present) - - val gradeAverageMode: String - get() = getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode) - - val gradeAverageForceCalc: Boolean - get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc) - - val isGradeExpandable: Boolean - get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade) - - val appThemeKey = context.getString(R.string.pref_key_app_theme) - val appTheme: String - get() = getString(appThemeKey, R.string.pref_default_app_theme) - - val gradeColorTheme: String - get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme) - - val appLanguageKey = context.getString(R.string.pref_key_app_language) - val appLanguage - get() = getString(appLanguageKey, R.string.pref_default_app_language) - - val serviceEnableKey = context.getString(R.string.pref_key_services_enable) - val isServiceEnabled: Boolean - get() = getBoolean(serviceEnableKey, R.bool.pref_default_services_enable) - - val servicesIntervalKey = context.getString(R.string.pref_key_services_interval) - val servicesInterval: Long - get() = getString(servicesIntervalKey, R.string.pref_default_services_interval).toLong() - - val servicesOnlyWifiKey = context.getString(R.string.pref_key_services_wifi_only) - val isServicesOnlyWifi: Boolean - get() = getBoolean(servicesOnlyWifiKey, R.bool.pref_default_services_wifi_only) - - val isNotificationsEnable: Boolean - get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable) - - val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug) - val isDebugNotificationEnable: Boolean - get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug) - - val gradePlusModifier: Double - get() = getString(R.string.pref_key_grade_modifier_plus, R.string.pref_default_grade_modifier_plus).toDouble() - - val gradeMinusModifier: Double - get() = getString(R.string.pref_key_grade_modifier_minus, R.string.pref_default_grade_modifier_minus).toDouble() - - val fillMessageContent: Boolean - get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content) - - val showWholeClassPlan: String - get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class) - - private fun getString(id: Int, default: Int) = getString(context.getString(id), default) - - private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default) - - private fun getBoolean(id: Int, default: Int) = getBoolean(context.getString(id), default) - - private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default)) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt deleted file mode 100644 index 9b1d4ac2..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import io.github.wulkanowy.data.db.dao.RecipientDao -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) { - - fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Maybe> { - return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() } - } - - fun saveRecipients(recipients: List): List { - return recipientDb.insertAll(recipients) - } - - fun deleteRecipients(recipients: List) { - recipientDb.deleteAll(recipients) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt deleted file mode 100644 index e5b16a15..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton -import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient - -@Singleton -class RecipientRemote @Inject constructor(private val sdk: Sdk) { - - fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Single> { - return sdk.init(student).getRecipients(unit.realId, role) - .map { recipients -> - recipients.map { it.toRecipient() } - } - } - - fun getMessageRecipients(student: Student, message: Message): Single> { - return sdk.init(student).getMessageRecipients(message.messageId, message.senderId) - .map { recipients -> - recipients.map { it.toRecipient() } - } - } - - private fun SdkRecipient.toRecipient(): Recipient { - return Recipient( - studentId = sdk.studentId, - realId = id, - realName = name, - name = shortName, - hash = hash, - loginId = loginId, - role = role, - unitId = reportingUnitId ?: 0 - ) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt deleted file mode 100644 index 6f8a72af..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecipientRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: RecipientLocal, - private val remote: RecipientRemote -) { - - fun getRecipients(student: Student, role: Int, unit: ReportingUnit, forceRefresh: Boolean = false): Single> { - return local.getRecipients(student, role, unit).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getRecipients(student, role, unit) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getRecipients(student, role, unit).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteRecipients(old.uniqueSubtract(new)) - local.saveRecipients(new.uniqueSubtract(old)) - } - }.flatMap { - local.getRecipients(student, role, unit).toSingle(emptyList()) - } - ) - } - - fun getMessageRecipients(student: Student, message: Message): Single> { - return ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getMessageRecipients(student, message) - else Single.error(UnknownHostException()) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt deleted file mode 100644 index f2789125..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.data.repositories.recover - -import io.github.wulkanowy.sdk.Sdk -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecoverRemote @Inject constructor(private val sdk: Sdk) { - - fun getReCaptchaSiteKey(host: String, symbol: String): Single> { - return sdk.getPasswordResetCaptchaCode(host, symbol) - } - - fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): Single { - return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) - } -} - diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt deleted file mode 100644 index 86d4ba1b..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.wulkanowy.data.repositories.recover - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecoverRepository @Inject constructor(private val settings: InternetObservingSettings, private val remote: RecoverRemote) { - - fun getReCaptchaSiteKey(host: String, symbol: String): Single> { - return ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getReCaptchaSiteKey(host, symbol) - else Single.error(UnknownHostException()) - } - } - - fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): Single { - return ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.sendRecoverRequest(url, symbol, email, reCaptchaResponse) - else Single.error(UnknownHostException()) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt deleted file mode 100644 index 6f9eec3f..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import io.github.wulkanowy.data.db.dao.ReportingUnitDao -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) { - - fun getReportingUnits(student: Student): Maybe> { - return reportingUnitDb.load(student.studentId).filter { !it.isEmpty() } - } - - fun getReportingUnit(student: Student, unitId: Int): Maybe { - return reportingUnitDb.loadOne(student.studentId, unitId) - } - - fun saveReportingUnits(reportingUnits: List): List { - return reportingUnitDb.insertAll(reportingUnits) - } - - fun deleteReportingUnits(reportingUnits: List) { - reportingUnitDb.deleteAll(reportingUnits) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt deleted file mode 100644 index 1fd8b08e..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitRemote @Inject constructor(private val sdk: Sdk) { - - fun getReportingUnits(student: Student): Single> { - return sdk.init(student).getReportingUnits().map { - it.map { unit -> - ReportingUnit( - studentId = sdk.studentId, - realId = unit.id, - roles = unit.roles, - senderId = unit.senderId, - senderName = unit.senderName, - shortName = unit.short - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt deleted file mode 100644 index ee544098..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Maybe -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: ReportingUnitLocal, - private val remote: ReportingUnitRemote -) { - - fun getReportingUnits(student: Student, forceRefresh: Boolean = false): Single> { - return local.getReportingUnits(student).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getReportingUnits(student) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getReportingUnits(student).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteReportingUnits(old.uniqueSubtract(new)) - local.saveReportingUnits(new.uniqueSubtract(old)) - } - }.flatMap { local.getReportingUnits(student).toSingle(emptyList()) } - ) - } - - fun getReportingUnit(student: Student, unitId: Int): Maybe { - return local.getReportingUnit(student, unitId) - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) getReportingUnits(student, true) - else Single.error(UnknownHostException()) - }.flatMapMaybe { - local.getReportingUnit(student, unitId) - } - ) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt deleted file mode 100644 index d8727287..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import io.github.wulkanowy.data.db.dao.SchoolDao -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.Semester -import io.reactivex.Maybe -import javax.inject.Inject - -class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) { - - fun saveSchool(school: School) { - schoolDb.insertAll(listOf(school)) - } - - fun deleteSchool(school: School) { - schoolDb.deleteAll(listOf(school)) - } - - fun getSchool(semester: Semester): Maybe { - return schoolDb.load(semester.studentId, semester.classId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt deleted file mode 100644 index 6a95a446..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject - -class SchoolRemote @Inject constructor(private val sdk: Sdk) { - - fun getSchoolInfo(student: Student, semester: Semester): Single { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getSchool() - .map { - School( - studentId = semester.studentId, - classId = semester.classId, - name = it.name, - address = it.address, - contact = it.contact, - headmaster = it.headmaster, - pedagogue = it.pedagogue - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt deleted file mode 100644 index 1715a28d..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SchoolRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: SchoolLocal, - private val remote: SchoolRemote -) { - - fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean = false): Maybe { - return local.getSchool(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getSchoolInfo(student, semester) - else Single.error(UnknownHostException()) - }.flatMapMaybe { new -> - local.getSchool(semester) - .doOnSuccess { old -> - if (new != old) { - local.deleteSchool(old) - local.saveSchool(new) - } - } - .doOnComplete { - local.saveSchool(new) - } - }.flatMap({ local.getSchool(semester) }, { Maybe.error(it) }, - { local.getSchool(semester) }) - ) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt deleted file mode 100644 index 3d98785c..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) { - - fun saveSemesters(semesters: List) { - semesterDb.insertAll(semesters) - } - - fun deleteSemesters(semesters: List) { - semesterDb.deleteAll(semesters) - } - - fun getSemesters(student: Student): Maybe> { - return semesterDb.loadAll(student.studentId, student.classId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt deleted file mode 100644 index 90f0e1d7..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SemesterRemote @Inject constructor(private val sdk: Sdk) { - - fun getSemesters(student: Student): Single> { - return sdk.init(student).getSemesters().map { semesters -> - semesters.map { - Semester( - studentId = student.studentId, - diaryId = it.diaryId, - diaryName = it.diaryName, - schoolYear = it.schoolYear, - semesterId = it.semesterId, - semesterName = it.semesterNumber, - start = it.start, - end = it.end, - classId = it.classId, - unitId = it.unitId - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt deleted file mode 100644 index 68946bee..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.isCurrent -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SemesterRepository @Inject constructor( - private val remote: SemesterRemote, - private val local: SemesterLocal, - private val settings: InternetObservingSettings -) { - - fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false): Single> { - return local.getSemesters(student).filter { !forceRefresh }.filter { semesters -> - when { - Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> semesters.firstOrNull { it.isCurrent }?.diaryId != 0 - refreshOnNoCurrent -> semesters.any { semester -> semester.isCurrent } - else -> true - } - }.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getSemesters(student) - else Single.error(UnknownHostException()) - }.flatMap { new -> - if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") - - local.getSemesters(student).toSingle(emptyList()).doOnSuccess { old -> - local.deleteSemesters(old.uniqueSubtract(new)) - local.saveSemesters(new.uniqueSubtract(old)) - } - }.flatMap { local.getSemesters(student).toSingle(emptyList()) }) - } - - fun getCurrentSemester(student: Student, forceRefresh: Boolean = false): Single { - return getSemesters(student, forceRefresh).map { it.getCurrentOrLast() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt deleted file mode 100644 index 06b9bb4a..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import android.content.Context -import io.github.wulkanowy.data.db.dao.StudentDao -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.security.decrypt -import io.github.wulkanowy.utils.security.encrypt -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class StudentLocal @Inject constructor( - private val studentDb: StudentDao, - private val context: Context -) { - - fun saveStudents(students: List): Single> { - return Single.fromCallable { - studentDb.insertAll(students.map { - if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) - else it - }) - } - } - - fun getStudents(decryptPass: Boolean): Maybe> { - return studentDb.loadAll() - .map { list -> list.map { it.apply { if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) } } } - .filter { it.isNotEmpty() } - } - - fun getStudentById(id: Int): Maybe { - return studentDb.loadById(id).map { - it.apply { - if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) - } - } - } - - fun getCurrentStudent(decryptPass: Boolean): Maybe { - return studentDb.loadCurrent().map { - it.apply { - if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) - } - } - } - - fun setCurrentStudent(student: Student): Completable { - return Completable.fromCallable { - studentDb.run { - resetCurrent() - updateCurrent(student.id) - } - } - } - - fun logoutStudent(student: Student): Completable { - return Completable.fromCallable { studentDb.delete(student) } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt deleted file mode 100644 index 9b77c9ad..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.reactivex.Single -import org.threeten.bp.LocalDateTime.now -import javax.inject.Inject -import javax.inject.Singleton -import io.github.wulkanowy.sdk.pojo.Student as SdkStudent - -@Singleton -class StudentRemote @Inject constructor(private val sdk: Sdk) { - - private fun mapStudents(students: List, email: String, password: String): List { - return students.map { student -> - Student( - email = email, - password = password, - isParent = student.isParent, - symbol = student.symbol, - studentId = student.studentId, - userLoginId = student.userLoginId, - studentName = student.studentName, - schoolSymbol = student.schoolSymbol, - schoolShortName = student.schoolShortName, - schoolName = student.schoolName, - className = student.className, - classId = student.classId, - scrapperBaseUrl = student.scrapperBaseUrl, - loginType = student.loginType.name, - isCurrent = false, - registrationDate = now(), - mobileBaseUrl = student.mobileBaseUrl, - privateKey = student.privateKey, - certificateKey = student.certificateKey, - loginMode = student.loginMode.name - ) - } - } - - fun getStudentsMobileApi(token: String, pin: String, symbol: String): Single> { - return sdk.getStudentsFromMobileApi(token, pin, symbol).map { mapStudents(it, "", "") } - } - - fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single> { - return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).map { mapStudents(it, email, password) } - } - - fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single> { - return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, symbol).map { mapStudents(it, email, password) } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt deleted file mode 100644 index bebd1eb9..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class StudentRepository @Inject constructor( - private val local: StudentLocal, - private val remote: StudentRemote, - private val settings: InternetObservingSettings -) { - - fun isStudentSaved(): Single = local.getStudents(false).isEmpty.map { !it } - - fun isCurrentStudentSet(): Single = local.getCurrentStudent(false).isEmpty.map { !it } - - fun getStudentsApi(pin: String, symbol: String, token: String): Single> { - return ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getStudentsMobileApi(token, pin, symbol) - else Single.error(UnknownHostException("No internet connection")) - } - } - - fun getStudentsScrapper(email: String, password: String, endpoint: String, symbol: String): Single> { - return ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getStudentsScrapper(email, password, endpoint, symbol) - else Single.error(UnknownHostException("No internet connection")) - } - } - - fun getStudentsHybrid(email: String, password: String, endpoint: String, symbol: String): Single> { - return ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getStudentsHybrid(email, password, endpoint, symbol) - else Single.error(UnknownHostException("No internet connection")) - } - } - - fun getSavedStudents(decryptPass: Boolean = true): Single> { - return local.getStudents(decryptPass).toSingle(emptyList()) - } - - fun getStudentById(id: Int): Single { - return local.getStudentById(id) - .switchIfEmpty(Maybe.error(NoCurrentStudentException())) - .toSingle() - } - - fun getCurrentStudent(decryptPass: Boolean = true): Single { - return local.getCurrentStudent(decryptPass) - .switchIfEmpty(Maybe.error(NoCurrentStudentException())) - .toSingle() - } - - fun saveStudents(students: List): Single> { - return local.saveStudents(students) - } - - fun switchStudent(student: Student): Completable { - return local.setCurrentStudent(student) - } - - fun logoutStudent(student: Student): Completable { - return local.logoutStudent(student) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt deleted file mode 100644 index f8c13e6c..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import io.github.wulkanowy.data.db.dao.SubjectDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Subject -import io.reactivex.Maybe -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectLocal @Inject constructor(private val subjectDao: SubjectDao) { - - fun getSubjects(semester: Semester): Maybe> { - return subjectDao.loadAll(semester.diaryId, semester.studentId) - .filter { it.isNotEmpty() } - } - - fun saveSubjects(subjects: List) { - subjectDao.insertAll(subjects) - } - - fun deleteSubjects(subjects: List) { - subjectDao.deleteAll(subjects) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt deleted file mode 100644 index d30232f8..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectRemote @Inject constructor(private val sdk: Sdk) { - - fun getSubjects(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getSubjects() - .map { subjects -> - subjects.map { - Subject( - studentId = semester.studentId, - diaryId = semester.diaryId, - name = it.name, - realId = it.id - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt deleted file mode 100644 index 649904da..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: SubjectLocal, - private val remote: SubjectRemote -) { - - fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false): Single> { - return local.getSubjects(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getSubjects(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getSubjects(semester) - .toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteSubjects(old.uniqueSubtract(new)) - local.saveSubjects(new.uniqueSubtract(old)) - } - }.flatMap { - local.getSubjects(semester).toSingle(emptyList()) - }) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt deleted file mode 100644 index dd2be5e5..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import io.github.wulkanowy.data.db.dao.TeacherDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Teacher -import io.reactivex.Maybe -import javax.inject.Inject - -class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) { - - fun saveTeachers(teachers: List) { - teacherDb.insertAll(teachers) - } - - fun deleteTeachers(teachers: List) { - teacherDb.deleteAll(teachers) - } - - fun getTeachers(semester: Semester): Maybe> { - return teacherDb.loadAll(semester.studentId, semester.classId).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt deleted file mode 100644 index 01552f74..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Teacher -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TeacherRemote @Inject constructor(private val sdk: Sdk) { - - fun getTeachers(student: Student, semester: Semester): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getTeachers(semester.semesterId) - .map { teachers -> - teachers.map { - Teacher( - studentId = semester.studentId, - name = it.name, - subject = it.subject, - shortName = it.short, - classId = semester.classId - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt deleted file mode 100644 index 3c10be73..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Teacher -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TeacherRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: TeacherLocal, - private val remote: TeacherRemote -) { - - fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean = false): Single> { - return local.getTeachers(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getTeachers(student, semester) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getTeachers(semester).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteTeachers(old.uniqueSubtract(new)) - local.saveTeachers(new.uniqueSubtract(old)) - } - }.flatMap { local.getTeachers(semester).toSingle(emptyList()) }) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt deleted file mode 100644 index e074ce2a..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import io.github.wulkanowy.data.db.dao.TimetableDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Timetable -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) { - - fun saveTimetable(timetables: List) { - timetableDb.insertAll(timetables) - } - - fun deleteTimetable(timetables: List) { - timetableDb.deleteAll(timetables) - } - - fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate).filter { it.isNotEmpty() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt deleted file mode 100644 index 22cb947d..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.reactivex.Single -import org.threeten.bp.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TimetableRemote @Inject constructor(private val sdk: Sdk) { - - fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getTimetable(startDate, endDate) - .map { lessons -> - lessons.map { - Timetable( - studentId = semester.studentId, - diaryId = semester.diaryId, - number = it.number, - start = it.start, - end = it.end, - date = it.date, - subject = it.subject, - subjectOld = it.subjectOld, - group = it.group, - room = it.room, - roomOld = it.roomOld, - teacher = it.teacher, - teacherOld = it.teacherOld, - info = it.info, - isStudentPlan = it.studentPlan, - changes = it.changes, - canceled = it.canceled - ) - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt deleted file mode 100644 index 42812b30..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Single -import org.threeten.bp.LocalDate -import java.net.UnknownHostException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TimetableRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: TimetableLocal, - private val remote: TimetableRemote -) { - - fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single> { - return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) -> - local.getTimetable(semester, monday, friday).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getTimetable(student, semester, monday, friday) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getTimetable(semester, monday, friday) - .toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteTimetable(old.uniqueSubtract(new)) - local.saveTimetable(new.uniqueSubtract(old).map { item -> - item.also { new -> - old.singleOrNull { new.start == it.start }?.let { old -> - return@map new.copy( - room = if (new.room.isEmpty()) old.room else new.room, - teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher - ) - } - } - }) - } - }.flatMap { - local.getTimetable(semester, monday, friday).toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt b/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt new file mode 100644 index 00000000..ba97d37a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.data.serializers + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.nullable +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.LocalDate + +@OptIn(ExperimentalSerializationApi::class) +object LocalDateSerializer : KSerializer { + + override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG).nullable + + override fun serialize(encoder: Encoder, value: LocalDate?) { + if (value == null) { + encoder.encodeNull() + } else { + encoder.encodeNotNullMark() + encoder.encodeLong(value.toEpochDay()) + } + } + + override fun deserialize(decoder: Decoder): LocalDate? = + if (decoder.decodeNotNullMark()) { + LocalDate.ofEpochDay(decoder.decodeLong()) + } else { + decoder.decodeNull() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt new file mode 100644 index 00000000..a95eab80 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.data.serializers + +import io.github.wulkanowy.data.enums.MessageType +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@OptIn(ExperimentalSerializationApi::class) +object SafeMessageTypeEnumListSerializer : KSerializer> { + + private val serializer = ListSerializer(String.serializer()) + + override val descriptor = serializer.descriptor + + override fun serialize(encoder: Encoder, value: List) { + encoder.encodeNotNullMark() + serializer.serialize(encoder, value.map { it.name }) + } + + override fun deserialize(decoder: Decoder): List = + serializer.deserialize(decoder).mapNotNull { enumName -> + MessageType.entries.find { it.name == enumName } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt b/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt deleted file mode 100644 index c0329f7d..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/AppComponent.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.di - -import dagger.Component -import dagger.android.AndroidInjector -import dagger.android.support.AndroidSupportInjectionModule -import io.github.wulkanowy.WulkanowyApp -import io.github.wulkanowy.data.RepositoryModule -import io.github.wulkanowy.services.ServicesModule -import javax.inject.Singleton - -@Singleton -@Component(modules = [ - AndroidSupportInjectionModule::class, - AppModule::class, - RepositoryModule::class, - ServicesModule::class, - BindingModule::class]) -interface AppComponent : AndroidInjector { - - @Component.Factory - interface Factory : AndroidInjector.Factory -} diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt index 4f568385..4efb4d84 100644 --- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt @@ -5,34 +5,23 @@ import android.content.Context import com.yariksoffice.lingver.Lingver import dagger.Module import dagger.Provides -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.WulkanowyApp -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.SchedulersProvider +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import io.github.wulkanowy.utils.DispatchersProvider import javax.inject.Singleton @Module +@InstallIn(SingletonComponent::class) internal class AppModule { @Singleton @Provides - fun provideContext(app: WulkanowyApp): Context = app + fun provideDispatchersProvider() = DispatchersProvider() @Singleton @Provides - fun provideSchedulersProvider() = SchedulersProvider() - - @Provides - fun provideFlexibleAdapter() = FlexibleAdapter>(null, null, true) - - @Singleton - @Provides - fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context) - - @Singleton - @Provides - fun provideAppInfo() = AppInfo() + fun provideAppWidgetManager(@ApplicationContext context: Context): AppWidgetManager = AppWidgetManager.getInstance(context) @Singleton @Provides diff --git a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt b/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt deleted file mode 100644 index 98061e60..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wulkanowy.di - -import dagger.Module -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.di.scopes.PerActivity -import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.ui.modules.login.LoginModule -import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity -import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainModule -import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity -import io.github.wulkanowy.ui.modules.splash.SplashActivity -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider - -@Suppress("unused") -@Module -internal abstract class BindingModule { - - @PerActivity - @ContributesAndroidInjector - abstract fun bindSplashActivity(): SplashActivity - - @PerActivity - @ContributesAndroidInjector(modules = [LoginModule::class]) - abstract fun bindLoginActivity(): LoginActivity - - @PerActivity - @ContributesAndroidInjector(modules = [MainModule::class]) - abstract fun bindMainActivity(): MainActivity - - @ContributesAndroidInjector - abstract fun bindMessageSendActivity(): SendMessageActivity - - @ContributesAndroidInjector - abstract fun bindTimetableWidgetAccountActivity(): TimetableWidgetConfigureActivity - - @ContributesAndroidInjector - abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider - - @ContributesAndroidInjector - abstract fun bindLuckyNumberWidgetAccountActivity(): LuckyNumberWidgetConfigureActivity - - @ContributesAndroidInjector - abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider -} diff --git a/app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.kt b/app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.kt deleted file mode 100644 index c1b4352a..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.wulkanowy.di.scopes - -import javax.inject.Scope - -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class PerActivity diff --git a/app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.kt b/app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.kt deleted file mode 100644 index 08884dac..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.wulkanowy.di.scopes - -import javax.inject.Scope - -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class PerChildFragment diff --git a/app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.kt b/app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.kt deleted file mode 100644 index 1203d7d8..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.wulkanowy.di.scopes - -import javax.inject.Scope - -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class PerFragment diff --git a/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt new file mode 100644 index 00000000..8b0d67b5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.domain.adminmessage + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.WulkanowyRepository +import io.github.wulkanowy.utils.AppInfo +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetAppropriateAdminMessageUseCase @Inject constructor( + private val wulkanowyRepository: WulkanowyRepository, + private val preferencesRepository: PreferencesRepository, + private val appInfo: AppInfo +) { + + operator fun invoke(student: Student, type: MessageType): Flow> { + return invoke(student.scrapperBaseUrl, type) + } + + operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow> { + return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages -> + adminMessages + .asSequence() + .filter { it.isNotDismissed() } + .filter { it.isVersionMatch() } + .filter { it.isRegisterHostMatch(scrapperBaseUrl) } + .filter { it.isFlavorMatch() } + .filter { it.isTypeMatch(type) } + .maxByOrNull { it.id } + } + } + + private fun AdminMessage.isNotDismissed(): Boolean { + return id !in preferencesRepository.dismissedAdminMessageIds + } + + private fun AdminMessage.isRegisterHostMatch(scrapperBaseUrl: String): Boolean { + return targetRegisterHost?.let { + scrapperBaseUrl.contains(it, true) + } ?: true + } + + private fun AdminMessage.isFlavorMatch(): Boolean { + return targetFlavor?.equals(appInfo.buildFlavor, true) ?: true + } + + private fun AdminMessage.isVersionMatch(): Boolean { + val isCorrectMaxVersion = versionMax?.let { it >= appInfo.versionCode } ?: true + val isCorrectMinVersion = versionMin?.let { it <= appInfo.versionCode } ?: true + + return isCorrectMaxVersion && isCorrectMinVersion + } + + private fun AdminMessage.isTypeMatch(messageType: MessageType): Boolean { + if (messageType in types) return true + if (MessageType.GENERAL_MESSAGE in types) return true + + return false + } +} diff --git a/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt new file mode 100644 index 00000000..294abd1b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt @@ -0,0 +1,106 @@ +package io.github.wulkanowy.domain.attendance + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode.* +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SubjectRepository +import io.github.wulkanowy.utils.allAbsences +import io.github.wulkanowy.utils.allPresences +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import kotlin.math.ceil +import kotlin.math.floor + +class GetAttendanceCalculatorDataUseCase @Inject constructor( + private val subjectRepository: SubjectRepository, + private val attendanceSummaryRepository: AttendanceSummaryRepository, + private val preferencesRepository: PreferencesRepository, +) { + + operator fun invoke( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ): Flow>> = + subjectRepository.getSubjects(student, semester, forceRefresh) + .mapResourceData { subjects -> subjects.sortedBy(Subject::name) } + .combineWithResourceData(preferencesRepository.targetAttendanceFlow, ::Pair) + .flatMapResourceData { (subjects, targetFreq) -> + combineResourceFlows(subjects.map { subject -> + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = subject.realId, + forceRefresh = forceRefresh + ).mapResourceData { summaries -> + summaries.toAttendanceData(subject.name, targetFreq) + } + }) + // Every individual combined flow causes separate network requests to update data. + // When there is N child flows, they can cause up to N-1 items to be emitted. Since all + // requests are usually completed in less than 5s, there is no need to emit multiple + // intermediates that will be visible for barely any time. + .debounceIntermediates() + } + .combineWithResourceData(preferencesRepository.attendanceCalculatorShowEmptySubjects) { attendanceDataList, showEmptySubjects -> + attendanceDataList.filter { it.total != 0 || showEmptySubjects } + } + .combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List::sortedBy) +} + +private fun List.toAttendanceData(subjectName: String, targetFreq: Int): AttendanceData { + val presences = sumOf { it.allPresences } + val absences = sumOf { it.allAbsences } + return AttendanceData( + subjectName = subjectName, + lessonBalance = calcLessonBalance( + targetFreq.toDouble() / 100, presences, absences + ), + presences = presences, + absences = absences, + ) +} + +private fun calcLessonBalance(targetFreq: Double, presences: Int, absences: Int): Int { + val total = presences + absences + // The `+ 1` is to avoid false positives in close cases. Eg.: + // target frequency 99%, 1 presence. Without the `+ 1` this would be reported shown as + // a positive balance of +1, however that is not actually true as skipping one class + // would make it so that the balance would actually be negative (-98). The `+ 1` + // fixes this and makes sure that in situations like these, it's not reporting incorrect + // balances + return when { + presences / (total + 1f) >= targetFreq -> calcMissingAbsences( + targetFreq, absences, presences + ) + presences / (total + 0f) < targetFreq -> -calcMissingPresences( + targetFreq, absences, presences + ) + else -> 0 + } +} + +private fun calcMissingPresences(targetFreq: Double, absences: Int, presences: Int) = + calcMinRequiredPresencesFor(targetFreq, absences) - presences + +private fun calcMinRequiredPresencesFor(targetFreq: Double, absences: Int) = + ceil((targetFreq / (1 - targetFreq)) * absences).toInt() + +private fun calcMissingAbsences(targetFreq: Double, absences: Int, presences: Int) = + calcMinRequiredAbsencesFor(targetFreq, presences) - absences + +private fun calcMinRequiredAbsencesFor(targetFreq: Double, presences: Int) = + floor((presences * (1 - targetFreq)) / targetFreq).toInt() + +private fun List.sortedBy(mode: AttendanceCalculatorSortingMode) = when (mode) { + ALPHABETIC -> sortedBy(AttendanceData::subjectName) + ATTENDANCE -> sortedByDescending(AttendanceData::presencePercentage) + LESSON_BALANCE -> sortedBy(AttendanceData::lessonBalance) +} diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt new file mode 100644 index 00000000..8f25eba1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.domain.messages + +import io.github.wulkanowy.data.db.dao.MailboxDao +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Student +import javax.inject.Inject + +class GetMailboxByStudentUseCase @Inject constructor( + private val mailboxDao: MailboxDao, +) { + + suspend operator fun invoke(student: Student): Mailbox? { + return mailboxDao.loadAll(student.email) + .filterByStudent(student) + } + + private fun List.filterByStudent(student: Student): Mailbox? { + val normalizedStudentName = student.studentName.normalizeStudentName() + + return singleOrNull { + it.studentName.normalizeStudentName() == normalizedStudentName + } ?: singleOrNull { + it.studentName.normalizeStudentName() == normalizedStudentName + && it.schoolNameShort == student.schoolShortName + } ?: singleOrNull { + it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() + } ?: singleOrNull { + it.studentName.getReversedName() == normalizedStudentName + } ?: singleOrNull { + it.studentName.getUnauthorizedVersion() == normalizedStudentName + } + } + + private fun String.normalizeStudentName(): String { + return trim().split(" ") + .filter { it.isNotBlank() } + .joinToString(" ") { part -> + part.lowercase().replaceFirstChar { it.uppercase() } + } + } + + private fun String.getFirstAndLastPart(): String { + val parts = normalizeStudentName().split(" ") + + val endParts = parts.filterIndexed { i, _ -> + i == 0 || parts.size - 1 == i + } + return endParts.joinToString(" ") + } + + private fun String.getReversedName(): String { + val parts = normalizeStudentName().split(" ") + + return parts + .asReversed() + .joinToString(" ") + } + + private fun String.getUnauthorizedVersion(): String { + return normalizeStudentName().split(" ") + .joinToString(" ") { + it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0)) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt new file mode 100644 index 00000000..ffd00574 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.domain.timetable + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import java.time.LocalDate +import javax.inject.Inject + +class IsStudentHasLessonsOnWeekendUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, + private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase, +) { + + suspend operator fun invoke( + semester: Semester, + currentDate: LocalDate = LocalDate.now(), + ): Boolean { + val lessons = timetableRepository.getTimetableFromDatabase( + semester = semester, + start = currentDate.monday, + end = currentDate.sunday, + ) + return isWeekendHasLessonsUseCase(lessons) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt new file mode 100644 index 00000000..908c9df7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.domain.timetable + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.DayOfWeek +import javax.inject.Inject + +class IsWeekendHasLessonsUseCase @Inject constructor() { + + operator fun invoke( + lessons: List, + ): Boolean = lessons.any { + it.date.dayOfWeek in listOf( + DayOfWeek.SATURDAY, + DayOfWeek.SUNDAY, + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index 7240a50b..1729f100 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -1,56 +1,70 @@ package io.github.wulkanowy.services +import android.app.AlarmManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import androidx.core.content.getSystemService import androidx.work.WorkManager -import com.squareup.inject.assisted.dagger2.AssistedModule import dagger.Binds import dagger.Module import dagger.Provides -import dagger.android.ContributesAndroidInjector +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel +import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel +import io.github.wulkanowy.services.sync.channels.NewConferencesChannel +import io.github.wulkanowy.services.sync.channels.NewExamChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel +import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel +import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel import io.github.wulkanowy.services.sync.channels.PushChannel +import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceWork import io.github.wulkanowy.services.sync.works.CompletedLessonWork +import io.github.wulkanowy.services.sync.works.ConferenceWork import io.github.wulkanowy.services.sync.works.ExamWork import io.github.wulkanowy.services.sync.works.GradeStatisticsWork -import io.github.wulkanowy.services.sync.works.GradeSummaryWork import io.github.wulkanowy.services.sync.works.GradeWork import io.github.wulkanowy.services.sync.works.HomeworkWork import io.github.wulkanowy.services.sync.works.LuckyNumberWork import io.github.wulkanowy.services.sync.works.MessageWork import io.github.wulkanowy.services.sync.works.NoteWork import io.github.wulkanowy.services.sync.works.RecipientWork +import io.github.wulkanowy.services.sync.works.SchoolAnnouncementWork import io.github.wulkanowy.services.sync.works.TeacherWork import io.github.wulkanowy.services.sync.works.TimetableWork import io.github.wulkanowy.services.sync.works.Work -import io.github.wulkanowy.services.widgets.TimetableWidgetService import javax.inject.Singleton @Suppress("unused") -@AssistedModule -@Module(includes = [AssistedInject_ServicesModule::class]) +@Module +@InstallIn(SingletonComponent::class) abstract class ServicesModule { companion object { @Provides - fun provideWorkManager(context: Context) = WorkManager.getInstance(context) + fun provideWorkManager(@ApplicationContext context: Context) = + WorkManager.getInstance(context) @Singleton @Provides - fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context) - } + fun provideNotificationManager(@ApplicationContext context: Context) = + NotificationManagerCompat.from(context) - @ContributesAndroidInjector - abstract fun bindTimetableWidgetService(): TimetableWidgetService + @Singleton + @Provides + fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager = + context.getSystemService()!! + } @Binds @IntoSet @@ -66,7 +80,7 @@ abstract class ServicesModule { @Binds @IntoSet - abstract fun provideGradeSummaryWork(work: GradeSummaryWork): Work + abstract fun provideConferenceWork(work: ConferenceWork): Work @Binds @IntoSet @@ -108,6 +122,10 @@ abstract class ServicesModule { @IntoSet abstract fun provideGradeStatistics(work: GradeStatisticsWork): Work + @Binds + @IntoSet + abstract fun provideSchoolAnnouncementWork(work: SchoolAnnouncementWork): Work + @Binds @IntoSet abstract fun provideDebugChannel(channel: DebugChannel): Channel @@ -116,6 +134,18 @@ abstract class ServicesModule { @IntoSet abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel + @Binds + @IntoSet + abstract fun provideNewConferenceChannel(channel: NewConferencesChannel): Channel + + @Binds + @IntoSet + abstract fun provideNewExamChannel(channel: NewExamChannel): Channel + + @Binds + @IntoSet + abstract fun provideNewHomeworkChannel(channel: NewHomeworkChannel): Channel + @Binds @IntoSet abstract fun provideNewGradesChannel(channel: NewGradesChannel): Channel @@ -128,7 +158,23 @@ abstract class ServicesModule { @IntoSet abstract fun provideNewNotesChannel(channel: NewNotesChannel): Channel + @Binds + @IntoSet + abstract fun provideNewSchoolAnnouncementChannel(channel: NewSchoolAnnouncementsChannel): Channel + @Binds @IntoSet abstract fun providePushChannel(channel: PushChannel): Channel + + @Binds + @IntoSet + abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel + + @Binds + @IntoSet + abstract fun provideChangeTimetableChannel(channel: TimetableChangeChannel): Channel + + @Binds + @IntoSet + abstract fun provideNewAttendanceChannel(channel: NewAttendanceChannel): Channel } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt new file mode 100644 index 00000000..01a583e1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -0,0 +1,160 @@ +package io.github.wulkanowy.services.alarm + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Build.VERSION_CODES.N +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.PendingIntentCompat +import io.github.wulkanowy.utils.getCompatColor +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.launchIn +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class TimetableNotificationReceiver : BroadcastReceiver() { + + @Inject + lateinit var studentRepository: StudentRepository + + @Inject + lateinit var preferencesRepository: PreferencesRepository + + companion object { + const val NOTIFICATION_TYPE_CURRENT = 1 + const val NOTIFICATION_TYPE_UPCOMING = 2 + const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 + + // FIXME only shows one notification even if there are multiple students. + // Probably want to fix after #721 is merged. + const val NOTIFICATION_ID = 2137 + + const val STUDENT_NAME = "student_name" + const val STUDENT_ID = "student_id" + const val LESSON_TYPE = "type" + const val LESSON_TITLE = "title" + const val LESSON_ROOM = "room" + const val LESSON_NEXT_TITLE = "next_title" + const val LESSON_NEXT_ROOM = "next_room" + const val LESSON_START = "start_timestamp" + const val LESSON_END = "end_timestamp" + } + + @OptIn(DelicateCoroutinesApi::class) + override fun onReceive(context: Context, intent: Intent) { + Timber.d("Receiving intent... ${intent.toUri(0)}") + + resourceFlow { + val showStudentName = !studentRepository.isOneUniqueStudent() + val student = studentRepository.getCurrentStudent(false) + val studentId = intent.getIntExtra(STUDENT_ID, 0) + + if (student.studentId == studentId) { + prepareNotification(context, intent, showStudentName) + } else { + Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") + } + } + .onResourceError { Timber.e(it) } + .launchIn(GlobalScope) + } + + private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { + val type = intent.getIntExtra(LESSON_TYPE, 0) + val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent + + if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) { + return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) + } + + val studentId = intent.getIntExtra(STUDENT_ID, 0) + val studentName = intent.getStringExtra(STUDENT_NAME).takeIf { showStudentName } + + val subject = intent.getStringExtra(LESSON_TITLE) + val room = intent.getStringExtra(LESSON_ROOM) + + val start = intent.getLongExtra(LESSON_START, 0) + val end = intent.getLongExtra(LESSON_END, 0) + + val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE) + val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM) + + Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: $start, student: $studentId") + + val notificationTitleResId = + if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next + val notificationTitle = + context.getString(notificationTitleResId, "($room) $subject".removePrefix("()")) + + val nextLessonText = nextSubject?.let { + context.getString( + R.string.timetable_later, + "($nextRoom) $nextSubject".removePrefix("()") + ) + } + + showNotification( + context = context, + isPersistent = isPersistent, + studentName = studentName, + countDown = if (type == NOTIFICATION_TYPE_CURRENT) end else start, + timeout = end - start, + title = notificationTitle, + next = nextLessonText + ) + } + + private fun showNotification( + context: Context, + isPersistent: Boolean, + studentName: String?, + countDown: Long, + timeout: Long, + title: String, + next: String? + ) { + NotificationManagerCompat.from(context) + .notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(next) + .setAutoCancel(false) + .setWhen(countDown) + .setOngoing(isPersistent) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .apply { + if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true) + } + .setTimeoutAfter(timeout) + .setSmallIcon(R.drawable.ic_stat_timetable) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setStyle(NotificationCompat.InboxStyle() + .addLine(next) + .also { inboxStyle -> + studentName?.let { inboxStyle.setSummaryText(it) } + }) + .setContentIntent( + PendingIntent.getActivity( + context, + NOTIFICATION_ID, + SplashActivity.getStartIntent(context, Destination.Timetable()), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + ) + .build() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt new file mode 100644 index 00000000..aae7882f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -0,0 +1,191 @@ +package io.github.wulkanowy.services.alarm + +import android.app.AlarmManager +import android.app.AlarmManager.RTC_WAKEUP +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.AlarmManagerCompat +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_END +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_START +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TYPE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_CURRENT +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME +import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.PendingIntentCompat +import io.github.wulkanowy.utils.nickOrName +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.time.Duration.ofMinutes +import java.time.Instant +import java.time.Instant.now +import java.time.LocalDate +import javax.inject.Inject + +class TimetableNotificationSchedulerHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val alarmManager: AlarmManager, + private val preferencesRepository: PreferencesRepository, + private val dispatchersProvider: DispatchersProvider, +) { + + private fun getRequestCode(time: Instant, studentId: Int): Int = + (time.toEpochMilli() * studentId).toInt() + + private fun getUpcomingLessonTime( + index: Int, + day: List, + lesson: Timetable + ): Instant = day.getOrNull(index - 1)?.end ?: lesson.start.minus(ofMinutes(30)) + + suspend fun cancelScheduled(lessons: List, student: Student) { + val studentId = student.studentId + withContext(dispatchersProvider.io) { + lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> + val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) + cancelScheduledTo( + range = upcomingTime..lesson.start, + requestCode = getRequestCode(upcomingTime, studentId) + ) + cancelScheduledTo( + range = lesson.start..lesson.end, + requestCode = getRequestCode(lesson.start, studentId) + ) + } + } + } + + private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { + if (now() in range) cancelNotification() + + alarmManager.cancel( + PendingIntent.getBroadcast( + context, + requestCode, + Intent(), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + ) + } + + fun cancelNotification() = + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) + + suspend fun scheduleNotifications(lessons: List, student: Student) { + if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) { + return cancelScheduled(lessons, student) + } + + if (!canScheduleExactAlarms()) { + Timber.w("Exact alarms are disabled by user") + preferencesRepository.isUpcomingLessonsNotificationsEnable = false + return + } + + if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) { + Timber.d("Timetable notification scheduling skipped - lessons are too far") + return + } + + withContext(dispatchersProvider.io) { + lessons.groupBy { it.date } + .map { it.value.sortedBy { lesson -> lesson.start } } + .map { it.filter { lesson -> lesson.isStudentPlan } } + .map { day -> + val canceled = day.filter { it.canceled } + val active = day.filter { !it.canceled } + + cancelScheduled(canceled, student) + active.forEachIndexed { index, lesson -> + val intent = createIntent(student, lesson, active.getOrNull(index + 1)) + + if (lesson.start > now()) { + scheduleBroadcast( + intent = intent, + studentId = student.studentId, + notificationType = NOTIFICATION_TYPE_UPCOMING, + time = getUpcomingLessonTime(index, active, lesson) + ) + } + + if (lesson.end > now()) { + scheduleBroadcast( + intent = intent, + studentId = student.studentId, + notificationType = NOTIFICATION_TYPE_CURRENT, + time = lesson.start + ) + if (active.lastIndex == index) { + scheduleBroadcast( + intent = intent, + studentId = student.studentId, + notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, + time = lesson.end + ) + } + } + } + } + } + } + + private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { + return Intent(context, TimetableNotificationReceiver::class.java).apply { + putExtra(STUDENT_ID, student.studentId) + putExtra(STUDENT_NAME, student.nickOrName) + putExtra(LESSON_ROOM, lesson.room) + putExtra(LESSON_START, lesson.start.toEpochMilli()) + putExtra(LESSON_END, lesson.end.toEpochMilli()) + putExtra(LESSON_TITLE, lesson.subject) + putExtra(LESSON_NEXT_TITLE, nextLesson?.subject) + putExtra(LESSON_NEXT_ROOM, nextLesson?.room) + } + } + + private fun scheduleBroadcast( + intent: Intent, + studentId: Int, + notificationType: Int, + time: Instant + ) { + try { + AlarmManagerCompat.setExactAndAllowWhileIdle( + alarmManager, RTC_WAKEUP, time.toEpochMilli(), + PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { + it.putExtra(LESSON_TYPE, notificationType) + }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) + ) + Timber.d( + "TimetableNotification scheduled: type: $notificationType, subject: ${ + intent.getStringExtra(LESSON_TITLE) + }, start: $time, student: $studentId" + ) + } catch (e: Throwable) { + Timber.e(e) + } + } + + fun canScheduleExactAlarms(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + alarmManager.canScheduleExactAlarms() + } catch (e: Throwable) { + false + } + } else true + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt b/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt new file mode 100644 index 00000000..3c173495 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.services.piggyback + +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.services.sync.SyncManager +import javax.inject.Inject + +@AndroidEntryPoint +class VulcanNotificationListenerService : NotificationListenerService() { + + @Inject + lateinit var syncManager: SyncManager + + @Inject + lateinit var preferenceRepository: PreferencesRepository + + override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) { + if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) { + syncManager.startOneTimeSyncWorker() + if (preferenceRepository.isNotificationPiggybackRemoveOriginalEnabled) { + cancelNotification(statusBarNotification.key) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt new file mode 100644 index 00000000..5e59aa54 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt @@ -0,0 +1,55 @@ +package io.github.wulkanowy.services.shortcuts + +import android.content.Context +import android.content.Intent +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { + + fun initializeShortcuts() { + val shortcutsInfo = listOf( + ShortcutInfoCompat.Builder(context, "grade_shortcut") + .setShortLabel(context.getString(R.string.grade_title)) + .setLongLabel(context.getString(R.string.grade_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade)) + .setIntent(SplashActivity.getStartIntent(context, Destination.Grade) + .apply { action = Intent.ACTION_VIEW }) + .build(), + + ShortcutInfoCompat.Builder(context, "attendance_shortcut") + .setShortLabel(context.getString(R.string.attendance_title)) + .setLongLabel(context.getString(R.string.attendance_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance)) + .setIntent(SplashActivity.getStartIntent(context, Destination.Attendance) + .apply { action = Intent.ACTION_VIEW }) + .build(), + + ShortcutInfoCompat.Builder(context, "exam_shortcut") + .setShortLabel(context.getString(R.string.exam_title)) + .setLongLabel(context.getString(R.string.exam_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam)) + .setIntent(SplashActivity.getStartIntent(context, Destination.Exam) + .apply { action = Intent.ACTION_VIEW }) + .build(), + + ShortcutInfoCompat.Builder(context, "timetable_shortcut") + .setShortLabel(context.getString(R.string.timetable_title)) + .setLongLabel(context.getString(R.string.timetable_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable)) + .setIntent(SplashActivity.getStartIntent(context, Destination.Timetable()) + .apply { action = Intent.ACTION_VIEW }) + .build() + ) + + shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index dda2abe0..e0a136f9 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -3,28 +3,22 @@ package io.github.wulkanowy.services.sync import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.O import androidx.core.app.NotificationManagerCompat +import androidx.lifecycle.asFlow +import androidx.work.* import androidx.work.BackoffPolicy.EXPONENTIAL -import androidx.work.Constraints -import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy.KEEP -import androidx.work.ExistingPeriodicWorkPolicy.REPLACE -import androidx.work.ExistingWorkPolicy +import androidx.work.ExistingPeriodicWorkPolicy.UPDATE import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.UNMETERED -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkInfo -import androidx.work.WorkManager -import com.paulinasadowska.rxworkmanagerobservers.extensions.getWorkInfoByIdObservable import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.isHolidays -import io.reactivex.Observable -import org.threeten.bp.LocalDate.now +import kotlinx.coroutines.flow.Flow import timber.log.Timber +import java.time.LocalDate.now import java.util.concurrent.TimeUnit.MINUTES import javax.inject.Inject import javax.inject.Singleton @@ -42,8 +36,9 @@ class SyncManager @Inject constructor( init { if (now().isHolidays) stopSyncWorker() - if (SDK_INT > O) { + if (SDK_INT >= O) { channels.forEach { it.create() } + notificationManager.deleteNotificationChannel("lesson_channel") notificationManager.deleteNotificationChannel("new_entries_channel") } @@ -56,28 +51,41 @@ class SyncManager @Inject constructor( fun startPeriodicSyncWorker(restart: Boolean = false) { if (preferencesRepository.isServiceEnabled && !now().isHolidays) { - workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, - PeriodicWorkRequestBuilder(preferencesRepository.servicesInterval, MINUTES) + val serviceInterval = preferencesRepository.servicesInterval + + workManager.enqueueUniquePeriodicWork( + SyncWorker::class.java.simpleName, if (restart) UPDATE else KEEP, + PeriodicWorkRequestBuilder(serviceInterval, MINUTES) .setInitialDelay(10, MINUTES) .setBackoffCriteria(EXPONENTIAL, 30, MINUTES) - .setConstraints(Constraints.Builder() - .setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED) - .build()) - .build()) + .setConstraints( + Constraints.Builder() + .setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED) + .build() + ) + .build() + ) } } - fun startOneTimeSyncWorker(): Observable { + // if quiet, no notifications will be sent + fun startOneTimeSyncWorker(quiet: Boolean = false): Flow { val work = OneTimeWorkRequestBuilder() .setInputData( Data.Builder() + .putBoolean("quiet", quiet) .putBoolean("one_time", true) .build() ) .build() - workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work) - return workManager.getWorkInfoByIdObservable(work.id) + workManager.enqueueUniqueWork( + "${SyncWorker::class.java.simpleName}_one_time", + ExistingWorkPolicy.REPLACE, + work + ) + + return workManager.getWorkInfoByIdLiveData(work.id).asFlow() } fun stopSyncWorker() { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 126d856a..bcbc23ef 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -5,26 +5,29 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.BigTextStyle import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationManagerCompat +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker import androidx.work.Data -import androidx.work.ListenableWorker -import androidx.work.RxWorker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import io.github.wulkanowy.R -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.sdk.exception.FeatureDisabledException +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.works.Work +import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCompatColor -import io.reactivex.Completable -import io.reactivex.Single +import kotlinx.coroutines.withContext import timber.log.Timber +import java.time.Instant import kotlin.random.Random +@HiltWorker class SyncWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted workerParameters: WorkerParameters, @@ -32,62 +35,80 @@ class SyncWorker @AssistedInject constructor( private val semesterRepository: SemesterRepository, private val works: Set<@JvmSuppressWildcards Work>, private val preferencesRepository: PreferencesRepository, - private val notificationManager: NotificationManagerCompat -) : RxWorker(appContext, workerParameters) { + private val notificationManager: NotificationManagerCompat, + private val dispatchersProvider: DispatchersProvider +) : CoroutineWorker(appContext, workerParameters) { - override fun createWork(): Single { + override suspend fun doWork(): Result = withContext(dispatchersProvider.io) { Timber.i("SyncWorker is starting") - return studentRepository.isCurrentStudentSet() - .filter { true } - .flatMap { studentRepository.getCurrentStudent().toMaybe() } - .flatMapCompletable { student -> - semesterRepository.getCurrentSemester(student, true) - .flatMapCompletable { semester -> - Completable.mergeDelayError(works.map { work -> - work.create(student, semester) - .onErrorResumeNext { - if (it is FeatureDisabledException || it is FeatureNotAvailableException) Completable.complete() - else Completable.error(it) - } - .doOnSubscribe { Timber.i("${work::class.java.simpleName} is starting") } - .doOnError { Timber.i("${work::class.java.simpleName} result: An exception occurred") } - .doOnComplete { Timber.i("${work::class.java.simpleName} result: Success") } - }) - } - } - .toSingleDefault(Result.success()) - .onErrorReturn { - Timber.e(it, "There was an error during synchronization") - when { - inputData.getBoolean("one_time", false) -> { - Result.failure(Data.Builder() - .putString("error", it.toString()) - .build() - ) - } - else -> Result.retry() + + if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure() + + val (student, semester) = try { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student, true) + student to semester + } catch (e: Throwable) { + Timber.e(e) + return@withContext getResultFromErrors(listOf(e)) + } + + val exceptions = works.mapNotNull { work -> + try { + Timber.i("${work::class.java.simpleName} is starting") + work.doWork(student, semester, isNotificationsEnabled()) + Timber.i("${work::class.java.simpleName} result: Success") + null + } catch (e: Throwable) { + Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") + if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) { + null + } else { + Timber.e(e) + e } } - .doOnSuccess { - if (preferencesRepository.isDebugNotificationEnable) notify(it) - Timber.i("SyncWorker result: $it") - } + } + val result = getResultFromErrors(exceptions) + + if (preferencesRepository.isDebugNotificationEnable) notify(result) + Timber.i("SyncWorker result: $result") + + return@withContext result + } + + private fun isNotificationsEnabled(): Boolean { + val quiet = inputData.getBoolean("quiet", false) + return preferencesRepository.isNotificationsEnable && !quiet + } + + private fun getResultFromErrors(errors: List): Result = when { + errors.isNotEmpty() && inputData.getBoolean("one_time", false) -> { + Result.failure( + Data.Builder() + .putString("error_message", errors.joinToString { it.message.toString() }) + .putString("error_stack", errors.map { it.stackTraceToString() }.toString()) + .build() + ) + } + errors.isNotEmpty() -> Result.retry() + else -> { + preferencesRepository.lasSyncDate = Instant.now() + Result.success() + } } private fun notify(result: Result) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) - .setContentTitle("Debug notification") - .setSmallIcon(R.drawable.ic_stat_push) - .setAutoCancel(true) - .setColor(applicationContext.getCompatColor(R.color.colorPrimary)) - .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) - .setPriority(PRIORITY_DEFAULT) - .build()) - } - - @AssistedInject.Factory - interface Factory { - - fun create(appContext: Context, workerParameters: WorkerParameters): ListenableWorker + notificationManager.notify( + Random.nextInt(Int.MAX_VALUE), + NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) + .setContentTitle("Debug notification") + .setSmallIcon(R.drawable.ic_stat_all) + .setAutoCancel(true) + .setColor(applicationContext.getCompatColor(R.color.colorPrimary)) + .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) + .setPriority(PRIORITY_DEFAULT) + .build() + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorkerFactory.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorkerFactory.kt deleted file mode 100644 index aadfc27f..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorkerFactory.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.wulkanowy.services.sync - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import timber.log.Timber -import javax.inject.Inject - -class SyncWorkerFactory @Inject constructor(private val syncWorkerFactory: SyncWorker.Factory) : WorkerFactory() { - - override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? { - return if (workerClassName == SyncWorker::class.java.name) { - syncWorkerFactory.create(appContext, workerParameters) - } else { - Timber.e(IllegalArgumentException("Unknown worker class name: $workerClassName")) - null - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt index 9b6f213b..2c756519 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/DebugChannel.kt @@ -6,6 +6,7 @@ import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject @@ -13,7 +14,7 @@ import javax.inject.Inject @TargetApi(26) class DebugChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context, + @ApplicationContext private val context: Context, private val appInfo: AppInfo ) : Channel { @@ -22,7 +23,7 @@ class DebugChannel @Inject constructor( } override fun create() { - if (appInfo.isDebug) return + if (!appInfo.isDebug) return notificationManager.createNotificationChannel( NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT) .apply { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/LuckyNumberChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/LuckyNumberChannel.kt index ae228edd..8025bd76 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/LuckyNumberChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/LuckyNumberChannel.kt @@ -6,13 +6,14 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import javax.inject.Inject @TargetApi(26) class LuckyNumberChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context + @ApplicationContext private val context: Context ) : Channel { companion object { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt new file mode 100644 index 00000000..3110099e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewAttendanceChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_attendance_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, + context.getString(R.string.channel_new_attendance), + NotificationManager.IMPORTANCE_HIGH + ) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewConferencesChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewConferencesChannel.kt new file mode 100644 index 00000000..397e67c6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewConferencesChannel.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewConferencesChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_conferences_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_conference), NotificationManager.IMPORTANCE_HIGH) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewExamChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewExamChannel.kt new file mode 100644 index 00000000..1bf5f855 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewExamChannel.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewExamChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_exam_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_exam), NotificationManager.IMPORTANCE_HIGH) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewGradesChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewGradesChannel.kt index 7d6f275d..5d5727d3 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewGradesChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewGradesChannel.kt @@ -6,13 +6,14 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import javax.inject.Inject @TargetApi(26) class NewGradesChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context + @ApplicationContext private val context: Context ) : Channel { companion object { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewHomeworkChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewHomeworkChannel.kt new file mode 100644 index 00000000..4b7dd0e9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewHomeworkChannel.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewHomeworkChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_homework_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_homework), NotificationManager.IMPORTANCE_HIGH) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewMessagesChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewMessagesChannel.kt index 49f39c6b..962fbfa2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewMessagesChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewMessagesChannel.kt @@ -6,13 +6,14 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import javax.inject.Inject @TargetApi(26) class NewMessagesChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context + @ApplicationContext private val context: Context ) : Channel { companion object { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewNotesChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewNotesChannel.kt index d80d43dc..c6e79b39 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewNotesChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewNotesChannel.kt @@ -6,13 +6,14 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import javax.inject.Inject @TargetApi(26) class NewNotesChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context + @ApplicationContext private val context: Context ) : Channel { companion object { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewSchoolAnnouncementsChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewSchoolAnnouncementsChannel.kt new file mode 100644 index 00000000..2b21ba40 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewSchoolAnnouncementsChannel.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewSchoolAnnouncementsChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_school_announcements_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_school_announcement), NotificationManager.IMPORTANCE_HIGH) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/PushChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/PushChannel.kt index a0e24ab4..1d7376b9 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/PushChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/PushChannel.kt @@ -6,13 +6,14 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import javax.inject.Inject @TargetApi(26) class PushChannel @Inject constructor( private val notificationManager: NotificationManagerCompat, - private val context: Context + @ApplicationContext private val context: Context ) : Channel { companion object { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt new file mode 100644 index 00000000..10dd3e00 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class TimetableChangeChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "change_timetable_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, + context.getString(R.string.channel_change_timetable), + NotificationManager.IMPORTANCE_HIGH + ) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt new file mode 100644 index 00000000..63b3a4f9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification.VISIBILITY_PUBLIC +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class UpcomingLessonsChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "upcoming_lesson_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_upcoming_lessons), IMPORTANCE_DEFAULT).apply { + lockscreenVisibility = VISIBILITY_PUBLIC + setShowBadge(false) + enableVibration(false) + setSound(null, null) + } + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt new file mode 100644 index 00000000..dadb68c5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt @@ -0,0 +1,179 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Notification +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.data.repositories.NotificationRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.PendingIntentCompat +import io.github.wulkanowy.utils.getCompatBitmap +import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.nickOrName +import java.time.Instant +import javax.inject.Inject +import kotlin.random.Random + +class AppNotificationManager @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context, + private val studentRepository: StudentRepository, + private val notificationRepository: NotificationRepository +) { + + @SuppressLint("InlinedApi") + suspend fun sendSingleNotification( + notificationData: NotificationData, + notificationType: NotificationType, + student: Student + ) { + val notification = NotificationCompat.Builder(context, notificationType.channel) + .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary)) + .setSmallIcon(R.drawable.ic_stat_all) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setContentIntent( + PendingIntent.getActivity( + context, + Random.nextInt(), + SplashActivity.getStartIntent(context, notificationData.destination), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + ) + .setContentTitle(notificationData.title) + .setContentText(notificationData.content) + .setStyle( + NotificationCompat.BigTextStyle() + .bigText(notificationData.content) + .also { builder -> + if (!studentRepository.isOneUniqueStudent()) { + builder.setSummaryText(student.nickOrName) + } + } + ) + .build() + + notificationManager.notify(Random.nextInt(), notification) + saveNotification(notificationData, notificationType, student) + } + + @SuppressLint("InlinedApi") + suspend fun sendMultipleNotifications( + groupNotificationData: GroupNotificationData, + student: Student + ) { + val notificationType = groupNotificationData.type + val groupType = notificationType.channel + val group = "${groupType}_${student.id}" + + sendSummaryNotification(groupNotificationData, group, student) + + groupNotificationData.notificationDataList.forEach { notificationData -> + val notification = NotificationCompat.Builder(context, notificationType.channel) + .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary)) + .setSmallIcon(R.drawable.ic_stat_all) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setContentIntent( + PendingIntent.getActivity( + context, + Random.nextInt(), + SplashActivity.getStartIntent(context, notificationData.destination), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + ) + .setContentTitle(notificationData.title) + .setContentText(notificationData.content) + .setStyle( + NotificationCompat.BigTextStyle() + .bigText(notificationData.content) + .also { builder -> + if (!studentRepository.isOneUniqueStudent()) { + builder.setSummaryText(student.nickOrName) + } + } + ) + .setGroup(group) + .build() + + notificationManager.notify(Random.nextInt(), notification) + saveNotification(notificationData, groupNotificationData.type, student) + } + } + + private suspend fun sendSummaryNotification( + groupNotificationData: GroupNotificationData, + group: String, + student: Student + ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return + + val summaryNotification = + NotificationCompat.Builder(context, groupNotificationData.type.channel) + .setContentTitle(groupNotificationData.title) + .setContentText(groupNotificationData.content) + .setSmallIcon(groupNotificationData.type.icon) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setStyle( + NotificationCompat.InboxStyle() + .also { builder -> + if (!studentRepository.isOneUniqueStudent()) { + builder.setSummaryText(student.nickOrName) + } + groupNotificationData.notificationDataList.forEach { + builder.addLine(it.content) + } + } + ) + .setContentIntent( + PendingIntent.getActivity( + context, + Random.nextInt(), + SplashActivity.getStartIntent(context, groupNotificationData.destination), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + ) + .setLocalOnly(true) + .setGroup(group) + .setGroupSummary(true) + .build() + + val groupId = student.id * 100 + groupNotificationData.type.ordinal + notificationManager.notify(groupId.toInt(), summaryNotification) + } + + private suspend fun saveNotification( + notificationData: NotificationData, + notificationType: NotificationType, + student: Student + ) { + val notificationEntity = Notification( + studentId = student.id, + title = notificationData.title, + content = notificationData.content, + destination = notificationData.destination, + type = notificationType, + date = Instant.now(), + ) + + notificationRepository.saveNotification(notificationEntity) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt new file mode 100644 index 00000000..43ae1fea --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt @@ -0,0 +1,120 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import java.time.Instant +import java.time.LocalDate +import javax.inject.Inject + +class ChangeTimetableNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context, +) { + + suspend fun notify(items: List, student: Student) { + val currentTime = Instant.now() + val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } + val lessonsByDate = changedLessons.groupBy { it.date } + val notificationDataList = lessonsByDate + .flatMap { (date, lessons) -> + getNotificationContents(date, lessons).map { + NotificationData( + title = context.getPlural( + R.plurals.timetable_notify_new_items_title, + 1 + ), + content = it, + destination = Destination.Timetable(date) + ) + } + } + .ifEmpty { return } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural( + R.plurals.timetable_notify_new_items_title, + changedLessons.size + ), + content = context.getPlural( + R.plurals.timetable_notify_new_items_group, + changedLessons.size, + changedLessons.size + ), + destination = Destination.Timetable(lessonsByDate.toSortedMap().firstKey()), + type = NotificationType.CHANGE_TIMETABLE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } + + private fun getNotificationContents(date: LocalDate, lessons: List): List { + val formattedDate = date.toFormattedString("EEE dd.MM") + + return if (lessons.size > 2) { + listOf( + context.getPlural( + R.plurals.timetable_notify_new_items, + lessons.size, + formattedDate, + lessons.size, + ) + ) + } else { + lessons.map { + buildString { + append( + context.getString( + R.string.timetable_notify_lesson, + formattedDate, + it.number, + it.subject + ) + ) + if (it.roomOld.isNotBlank()) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_room, + it.roomOld, + it.room + ) + ) + } + if (it.teacherOld.isNotBlank() && it.teacher != it.teacherOld) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_teacher, + it.teacherOld, + it.teacher + ) + ) + } + if (it.subjectOld.isNotBlank()) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_subject, + it.subjectOld, + it.subject + ) + ) + } + if (it.info.isNotBlank()) { + appendLine() + append(it.info) + } + } + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt new file mode 100644 index 00000000..99473a8e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt @@ -0,0 +1,55 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class NewAttendanceNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val lines = items.filterNot { it.presence || it.name == "UNKNOWN" } + .map { + val lesson = it.subject.ifBlank { "Lekcja ${it.number}" } + val description = context.getString(it.descriptionRes) + "${it.date.toFormattedString("dd.MM")} - $lesson: $description" + } + .ifEmpty { return } + + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1), + content = it, + destination = Destination.Attendance + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural( + R.plurals.attendance_notify_new_items_title, + notificationDataList.size + ), + content = context.getPlural( + R.plurals.attendance_notify_new_items, + notificationDataList.size, + notificationDataList.size + ), + destination = Destination.Attendance, + type = NotificationType.NEW_ATTENDANCE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt new file mode 100644 index 00000000..92977ebb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import java.time.Instant +import javax.inject.Inject + +class NewConferenceNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val today = Instant.now() + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" + } + .ifEmpty { return } + + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.conference_notify_new_item_title, 1), + content = it, + destination = Destination.Conference + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.conference_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.conference_notify_new_items, + lines.size, + lines.size + ), + destination = Destination.Conference, + type = NotificationType.NEW_CONFERENCE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt new file mode 100644 index 00000000..125bbf92 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import javax.inject.Inject + +class NewExamNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val today = LocalDate.now() + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}" + } + .ifEmpty { return } + + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.exam_notify_new_item_title, 1), + content = it, + destination = Destination.Exam, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.exam_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.exam_notify_new_item_content, + lines.size, + lines.size + ), + destination = Destination.Exam, + type = NotificationType.NEW_EXAM + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt new file mode 100644 index 00000000..232ba672 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt @@ -0,0 +1,117 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.utils.getPlural +import javax.inject.Inject + +class NewGradeNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notifyDetails(items: List, student: Student) { + val notificationDataList = items + .filter { !it.isNotified } + .map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items, 1), + content = buildString { + append("${it.subject}: ${it.entry}") + if (it.comment.isNotBlank()) append(" (${it.comment})") + }, + destination = Destination.Grade, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items, items.size), + content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size), + destination = Destination.Grade, + type = NotificationType.NEW_GRADE_DETAILS + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } + + suspend fun notifyPredicted(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_predicted, 1), + content = "${it.subject}: ${it.predictedGrade}", + destination = Destination.Grade, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_predicted, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_predicted, + items.size, + items.size + ), + destination = Destination.Grade, + type = NotificationType.NEW_GRADE_PREDICTED + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } + + suspend fun notifyFinal(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_final, 1), + content = "${it.subject}: ${it.finalGrade}", + destination = Destination.Grade, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_final, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_final, + items.size, + items.size + ), + destination = Destination.Grade, + type = NotificationType.NEW_GRADE_FINAL + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } + + suspend fun notifyDescriptive(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_descriptive, 1), + content = "${it.subject}: ${it.description}", + destination = Destination.Grade, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_descriptive, + items.size, + items.size + ), + destination = Destination.Grade, + type = NotificationType.NEW_GRADE_DESCRIPTIVE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt new file mode 100644 index 00000000..856c5158 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import javax.inject.Inject + +class NewHomeworkNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val today = LocalDate.now() + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}" + } + .ifEmpty { return } + + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.homework_notify_new_item_title, 1), + content = it, + destination = Destination.Homework, + ) + } + + val groupNotificationData = GroupNotificationData( + title = context.getPlural(R.plurals.homework_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.homework_notify_new_item_content, + lines.size, + lines.size + ), + destination = Destination.Homework, + type = NotificationType.NEW_HOMEWORK, + notificationDataList = notificationDataList + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt new file mode 100644 index 00000000..bbe9b8a1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import javax.inject.Inject + +class NewLuckyNumberNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(item: LuckyNumber, student: Student) { + val notificationData = NotificationData( + title = context.getString(R.string.lucky_number_notify_new_item_title), + content = context.getString( + R.string.lucky_number_notify_new_item, + item.luckyNumber.toString() + ), + destination = Destination.LuckyNumber + ) + + appNotificationManager.sendSingleNotification( + notificationData = notificationData, + notificationType = NotificationType.NEW_LUCKY_NUMBER, + student = student + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt new file mode 100644 index 00000000..45523d51 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.utils.getPlural +import javax.inject.Inject + +class NewMessageNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.message_new_items, 1), + content = "${it.correspondents}: ${it.subject}", + destination = Destination.Message, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.message_new_items, items.size), + content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size), + destination = Destination.Message, + type = NotificationType.NEW_MESSAGE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt new file mode 100644 index 00000000..dae7d433 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.getPlural +import javax.inject.Inject + +class NewNoteNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val notificationDataList = items.map { + val titleRes = when (NoteCategory.getByValue(it.categoryType)) { + NoteCategory.POSITIVE -> R.plurals.praise_new_items + NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items + else -> R.plurals.note_new_items + } + + NotificationData( + title = context.getPlural(titleRes, 1), + content = "${it.teacher}: ${it.category}", + destination = Destination.Note, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + destination = Destination.Note, + title = context.getPlural(R.plurals.note_new_items, items.size), + content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size), + type = NotificationType.NEW_NOTE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt new file mode 100644 index 00000000..cc7e4656 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt @@ -0,0 +1,49 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import androidx.core.text.parseAsHtml +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.getPlural +import javax.inject.Inject + +class NewSchoolAnnouncementNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + destination = Destination.SchoolAnnouncement, + title = context.getPlural( + R.plurals.school_announcement_notify_new_item_title, + 1 + ), + content = "${it.subject}: ${it.content.parseAsHtml()}" + ) + } + val groupNotificationData = GroupNotificationData( + type = NotificationType.NEW_ANNOUNCEMENT, + destination = Destination.SchoolAnnouncement, + title = context.getPlural( + R.plurals.school_announcement_notify_new_item_title, + items.size + ), + content = context.getPlural( + R.plurals.school_announcement_notify_new_items, + items.size, + items.size + ), + notificationDataList = notificationDataList + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt new file mode 100644 index 00000000..4e7f2735 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt @@ -0,0 +1,76 @@ +package io.github.wulkanowy.services.sync.notifications + +import io.github.wulkanowy.R +import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel +import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel +import io.github.wulkanowy.services.sync.channels.NewConferencesChannel +import io.github.wulkanowy.services.sync.channels.NewExamChannel +import io.github.wulkanowy.services.sync.channels.NewGradesChannel +import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel +import io.github.wulkanowy.services.sync.channels.NewMessagesChannel +import io.github.wulkanowy.services.sync.channels.NewNotesChannel +import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel +import io.github.wulkanowy.services.sync.channels.PushChannel +import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel + +enum class NotificationType( + val channel: String, + val icon: Int +) { + NEW_CONFERENCE( + channel = NewConferencesChannel.CHANNEL_ID, + icon = R.drawable.ic_more_conferences, + ), + NEW_EXAM( + channel = NewExamChannel.CHANNEL_ID, + icon = R.drawable.ic_main_exam + ), + NEW_GRADE_DETAILS( + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_GRADE_PREDICTED( + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_GRADE_FINAL( + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_GRADE_DESCRIPTIVE( + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_HOMEWORK( + channel = NewHomeworkChannel.CHANNEL_ID, + icon = R.drawable.ic_more_homework, + ), + NEW_LUCKY_NUMBER( + channel = LuckyNumberChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_luckynumber, + ), + NEW_MESSAGE( + channel = NewMessagesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_message, + ), + NEW_NOTE( + channel = NewNotesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_note + ), + NEW_ANNOUNCEMENT( + channel = NewSchoolAnnouncementsChannel.CHANNEL_ID, + icon = R.drawable.ic_all_about + ), + CHANGE_TIMETABLE( + channel = TimetableChangeChannel.CHANNEL_ID, + icon = R.drawable.ic_main_timetable + ), + NEW_ATTENDANCE( + channel = NewAttendanceChannel.CHANNEL_ID, + icon = R.drawable.ic_main_attendance + ), + PUSH( + channel = PushChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_all + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt index 5f7d7efa..55ce7e90 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt @@ -2,16 +2,20 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.waitForResult import javax.inject.Inject class AttendanceSummaryWork @Inject constructor( private val attendanceSummaryRepository: AttendanceSummaryRepository ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true).ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = -1, + forceRefresh = true, + ).waitForResult() } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index e8579ddb..4fc09749 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -2,17 +2,46 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.reactivex.Completable -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.data.repositories.AttendanceRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification +import io.github.wulkanowy.utils.previousOrSameSchoolDay +import kotlinx.coroutines.flow.first +import java.time.LocalDate.now import javax.inject.Inject -class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work { +class AttendanceWork @Inject constructor( + private val attendanceRepository: AttendanceRepository, + private val newAttendanceNotification: NewAttendanceNotification, +) : Work { - override fun create(student: Student, semester: Semester): Completable { - return attendanceRepository.getAttendance(student, semester, now().monday, now().friday, true) - .ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().previousOrSameSchoolDay + val endDate = startDate.plusDays(7) + + attendanceRepository.getAttendance( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + notify = notify, + ) + .waitForResult() + + attendanceRepository.getAttendanceFromDatabase( + semester = semester, + start = startDate, + end = endDate, + ) + .first() + .filterNot { it.isNotified } + .let { + if (it.isNotEmpty()) newAttendanceNotification.notify(it, student) + + attendanceRepository.updateTimetable(it.onEach { attendance -> + attendance.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index 0da597e0..f898aa04 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -2,20 +2,24 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.data.repositories.CompletedLessonsRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.utils.monday -import io.reactivex.Completable -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.utils.sunday +import java.time.LocalDate.now import javax.inject.Inject class CompletedLessonWork @Inject constructor( private val completedLessonsRepository: CompletedLessonsRepository ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().friday, true) - .ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + completedLessonsRepository.getCompletedLessons( + student = student, + semester = semester, + start = now().monday, + end = now().sunday, + forceRefresh = true, + ).waitForResult() } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt new file mode 100644 index 00000000..c85c0043 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.services.sync.works + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification +import kotlinx.coroutines.flow.first +import javax.inject.Inject + +class ConferenceWork @Inject constructor( + private val conferenceRepository: ConferenceRepository, + private val newConferenceNotification: NewConferenceNotification, +) : Work { + + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + conferenceRepository.getConferences( + student = student, + semester = semester, + forceRefresh = true, + notify = notify + ).waitForResult() + + conferenceRepository.getConferenceFromDatabase(semester).first() + .filter { !it.isNotified }.let { + if (it.isNotEmpty()) newConferenceNotification.notify(it, student) + + conferenceRepository.updateConference(it.onEach { conference -> + conference.isNotified = true + }) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index c6110bbb..4b0b1bdb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -2,16 +2,43 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.exam.ExamRepository -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.reactivex.Completable -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewExamNotification +import io.github.wulkanowy.utils.endExamsDay +import io.github.wulkanowy.utils.startExamsDay +import kotlinx.coroutines.flow.first +import java.time.LocalDate.now import javax.inject.Inject -class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work { +class ExamWork @Inject constructor( + private val examRepository: ExamRepository, + private val newExamNotification: NewExamNotification, +) : Work { - override fun create(student: Student, semester: Semester): Completable { - return examRepository.getExams(student, semester, now().monday, now().friday, true).ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().startExamsDay + val endDate = startDate.endExamsDay + + examRepository.getExams( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + notify = notify, + ).waitForResult() + + examRepository.getExamsFromDatabase( + semester = semester, + start = startDate, + end = endDate, + ) + .first() + .filter { !it.isNotified }.let { + if (it.isNotEmpty()) newExamNotification.notify(it, student) + + examRepository.updateExam(it.onEach { exam -> exam.isNotified = true }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 327c7174..ac35bc9a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -2,15 +2,20 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.GradeStatisticsRepository +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject -class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work { +class GradeStatisticsWork @Inject constructor( + private val gradeStatisticsRepository: GradeStatisticsRepository +) : Work { - override fun create(student: Student, semester: Semester): Completable { - return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, true) - .ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + with(gradeStatisticsRepository) { + getGradesPartialStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() + getGradesSemesterStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() + getGradesPointsStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() + } } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt deleted file mode 100644 index 4c8e955d..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeSummaryWork.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.services.sync.works - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.reactivex.Completable -import javax.inject.Inject - -class GradeSummaryWork @Inject constructor(private val gradeSummaryRepository: GradeSummaryRepository) : Work { - - override fun create(student: Student, semester: Semester): Completable { - return gradeSummaryRepository.getGradesSummary(student, semester, true).ignoreElement() - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 7559d289..b62ad94b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -1,61 +1,59 @@ package io.github.wulkanowy.services.sync.works -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationCompat.DEFAULT_ALL -import androidx.core.app.NotificationCompat.PRIORITY_HIGH -import androidx.core.app.NotificationManagerCompat -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.services.sync.channels.NewGradesChannel -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.getCompatColor -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewGradeNotification +import kotlinx.coroutines.flow.first import javax.inject.Inject -import kotlin.random.Random class GradeWork @Inject constructor( - private val context: Context, - private val notificationManager: NotificationManagerCompat, private val gradeRepository: GradeRepository, - private val preferencesRepository: PreferencesRepository + private val newGradeNotification: NewGradeNotification, ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable) - .flatMap { gradeRepository.getNotNotifiedGrades(semester) } - .flatMapCompletable { - if (it.isNotEmpty()) notify(it) + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + gradeRepository.getGrades( + student = student, + semester = semester, + forceRefresh = true, + notify = notify, + ).waitForResult() + + gradeRepository.getGradesFromDatabase(semester).first() + .filter { !it.isNotified }.let { + if (it.isNotEmpty()) newGradeNotification.notifyDetails(it, student) + gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true }) } - } - private fun notify(grades: List) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID) - .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size)) - .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size)) - .setSmallIcon(R.drawable.ic_stat_grade) - .setAutoCancel(true) - .setPriority(PRIORITY_HIGH) - .setDefaults(DEFAULT_ALL) - .setColor(context.getCompatColor(R.color.colorPrimary)) - .setContentIntent( - PendingIntent.getActivity(context, MainView.Section.GRADE.id, - MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT)) - .setStyle(NotificationCompat.InboxStyle().run { - setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) - grades.forEach { addLine("${it.subject}: ${it.entry}") } - this - }) - .build() - ) + gradeRepository.getGradesPredictedFromDatabase(semester).first() + .filter { !it.isPredictedGradeNotified }.let { + if (it.isNotEmpty()) newGradeNotification.notifyPredicted(it, student) + + gradeRepository.updateGradesSummary(it.onEach { grade -> + grade.isPredictedGradeNotified = true + }) + } + + gradeRepository.getGradesFinalFromDatabase(semester).first() + .filter { !it.isFinalGradeNotified }.let { + if (it.isNotEmpty()) newGradeNotification.notifyFinal(it, student) + + gradeRepository.updateGradesSummary(it.onEach { grade -> + grade.isFinalGradeNotified = true + }) + } + + gradeRepository.getGradesDescriptiveFromDatabase(semester).first() + .filter { !it.isNotified } + .let { + if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student) + + gradeRepository.updateGradesDescriptive(it.onEach { grade -> + grade.isNotified = true + }) + } } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index cf348439..ddff3af7 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -2,16 +2,46 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification import io.github.wulkanowy.utils.monday -import io.reactivex.Completable -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.sunday +import kotlinx.coroutines.flow.first +import java.time.LocalDate.now import javax.inject.Inject -class HomeworkWork @Inject constructor(private val homeworkRepository: HomeworkRepository) : Work { +class HomeworkWork @Inject constructor( + private val homeworkRepository: HomeworkRepository, + private val newHomeworkNotification: NewHomeworkNotification, +) : Work { - override fun create(student: Student, semester: Semester): Completable { - return homeworkRepository.getHomework(student, semester, now().monday, now().friday, true).ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().nextOrSameSchoolDay.monday + val endDate = startDate.sunday + + homeworkRepository.getHomework( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + notify = notify, + ).waitForResult() + + homeworkRepository.getHomeworkFromDatabase( + semester = semester, + start = startDate, + end = endDate + ) + .first() + .filter { !it.isNotified }.let { + if (it.isNotEmpty()) newHomeworkNotification.notify(it, student) + + homeworkRepository.updateHomework(it.onEach { homework -> + homework.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt index f19af4df..668b1b6b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt @@ -1,54 +1,27 @@ package io.github.wulkanowy.services.sync.works -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationCompat.DEFAULT_ALL -import androidx.core.app.NotificationCompat.PRIORITY_HIGH -import androidx.core.app.NotificationManagerCompat -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.getCompatColor -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification import javax.inject.Inject -import kotlin.random.Random class LuckyNumberWork @Inject constructor( - private val context: Context, - private val notificationManager: NotificationManagerCompat, private val luckyNumberRepository: LuckyNumberRepository, - private val preferencesRepository: PreferencesRepository + private val newLuckyNumberNotification: NewLuckyNumberNotification, ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable) - .flatMap { luckyNumberRepository.getNotNotifiedLuckyNumber(student) } - .flatMapCompletable { - notify(it) - luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true }) - } - } + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + luckyNumberRepository.getLuckyNumber( + student = student, + forceRefresh = true, + notify = notify, + ).waitForResult() - private fun notify(luckyNumber: LuckyNumber) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, LuckyNumberChannel.CHANNEL_ID) - .setContentTitle(context.getString(R.string.lucky_number_notify_new_item_title)) - .setContentText(context.getString(R.string.lucky_number_notify_new_item, luckyNumber.luckyNumber)) - .setSmallIcon(R.drawable.ic_stat_luckynumber) - .setAutoCancel(true) - .setDefaults(DEFAULT_ALL) - .setPriority(PRIORITY_HIGH) - .setColor(context.getCompatColor(R.color.colorPrimary)) - .setContentIntent( - PendingIntent.getActivity(context, MainView.Section.MESSAGE.id, - MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)) - .build()) + luckyNumberRepository.getNotNotifiedLuckyNumber(student)?.let { + newLuckyNumberNotification.notify(it, student) + luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index b8773dbc..c7824e61 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -1,61 +1,33 @@ package io.github.wulkanowy.services.sync.works -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationCompat.DEFAULT_ALL -import androidx.core.app.NotificationCompat.PRIORITY_HIGH -import androidx.core.app.NotificationManagerCompat -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.services.sync.channels.NewMessagesChannel -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.getCompatColor -import io.reactivex.Completable +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewMessageNotification +import kotlinx.coroutines.flow.first import javax.inject.Inject -import kotlin.random.Random class MessageWork @Inject constructor( - private val context: Context, - private val notificationManager: NotificationManagerCompat, private val messageRepository: MessageRepository, - private val preferencesRepository: PreferencesRepository + private val newMessageNotification: NewMessageNotification, ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return messageRepository.getMessages(student, semester, RECEIVED, true, preferencesRepository.isNotificationsEnable) - .flatMap { messageRepository.getNotNotifiedMessages(student) } - .flatMapCompletable { - if (it.isNotEmpty()) notify(it) + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val mailbox = messageRepository.getMailboxByStudent(student) + messageRepository.getMessages( + student = student, + mailbox = mailbox, + folder = RECEIVED, + forceRefresh = true, + notify = notify + ).waitForResult() + + messageRepository.getMessagesFromDatabase(student, mailbox).first() + .filter { !it.isNotified && it.unread }.let { + if (it.isNotEmpty()) newMessageNotification.notify(it, student) messageRepository.updateMessages(it.onEach { message -> message.isNotified = true }) } } - - private fun notify(messages: List) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewMessagesChannel.CHANNEL_ID) - .setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, messages.size, messages.size)) - .setContentText(context.resources.getQuantityString(R.plurals.message_notify_new_items, messages.size, messages.size)) - .setSmallIcon(R.drawable.ic_stat_message) - .setAutoCancel(true) - .setDefaults(DEFAULT_ALL) - .setPriority(PRIORITY_HIGH) - .setColor(context.getCompatColor(R.color.colorPrimary)) - .setContentIntent( - PendingIntent.getActivity(context, MainView.Section.MESSAGE.id, - MainActivity.getStartIntent(context, MainView.Section.MESSAGE, true), FLAG_UPDATE_CURRENT) - ) - .setStyle(NotificationCompat.InboxStyle().run { - setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, messages.size, messages.size)) - messages.forEach { addLine("${it.sender}: ${it.subject}") } - this - }) - .build()) - } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt index 4fc92ffc..df6e2b06 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt @@ -1,60 +1,32 @@ package io.github.wulkanowy.services.sync.works -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationCompat.DEFAULT_ALL -import androidx.core.app.NotificationCompat.PRIORITY_HIGH -import androidx.core.app.NotificationManagerCompat -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.note.NoteRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.services.sync.channels.NewNotesChannel -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.getCompatColor -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.NoteRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewNoteNotification +import kotlinx.coroutines.flow.first import javax.inject.Inject -import kotlin.random.Random class NoteWork @Inject constructor( - private val context: Context, - private val notificationManager: NotificationManagerCompat, private val noteRepository: NoteRepository, - private val preferencesRepository: PreferencesRepository + private val newNoteNotification: NewNoteNotification, ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return noteRepository.getNotes(student, semester, true, preferencesRepository.isNotificationsEnable) - .flatMap { noteRepository.getNotNotifiedNotes(student) } - .flatMapCompletable { - if (it.isNotEmpty()) notify(it) + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + noteRepository.getNotes( + student = student, + semester = semester, + forceRefresh = true, + notify = notify, + ).waitForResult() + + noteRepository.getNotesFromDatabase(student).first() + .filter { !it.isNotified }.let { + if (it.isNotEmpty()) newNoteNotification.notify(it, student) + noteRepository.updateNotes(it.onEach { note -> note.isNotified = true }) } } - - private fun notify(notes: List) { - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID) - .setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)) - .setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)) - .setSmallIcon(R.drawable.ic_stat_note) - .setAutoCancel(true) - .setDefaults(DEFAULT_ALL) - .setPriority(PRIORITY_HIGH) - .setColor(context.getCompatColor(R.color.colorPrimary)) - .setContentIntent( - PendingIntent.getActivity(context, MainView.Section.NOTE.id, - MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT)) - .setStyle(NotificationCompat.InboxStyle().run { - setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)) - notes.forEach { addLine("${it.teacher}: ${it.category}") } - this - }) - .build()) - } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt index fa610dee..90b20651 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt @@ -1,24 +1,23 @@ package io.github.wulkanowy.services.sync.works +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.recipient.RecipientRepository -import io.github.wulkanowy.data.repositories.reportingunit.ReportingUnitRepository -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.toFirstResult import javax.inject.Inject class RecipientWork @Inject constructor( - private val reportingUnitRepository: ReportingUnitRepository, + private val messageRepository: MessageRepository, private val recipientRepository: RecipientRepository ) : Work { - override fun create(student: Student, semester: Semester): Completable { - return reportingUnitRepository.getReportingUnits(student, true) - .flatMapCompletable { units -> - Completable.mergeDelayError(units.map { - recipientRepository.getRecipients(student, 2, it, true).ignoreElement() - }) - } + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult() + mailboxes.dataOrNull?.forEach { + recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE) + } } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt new file mode 100644 index 00000000..1aedc839 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt @@ -0,0 +1,37 @@ +package io.github.wulkanowy.services.sync.works + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification +import kotlinx.coroutines.flow.first +import java.time.LocalDate +import javax.inject.Inject + +class SchoolAnnouncementWork @Inject constructor( + private val schoolAnnouncementRepository: SchoolAnnouncementRepository, + private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification, +) : Work { + + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + schoolAnnouncementRepository.getSchoolAnnouncements( + student = student, + forceRefresh = true, + notify = notify, + ).waitForResult() + + schoolAnnouncementRepository.getSchoolAnnouncementFromDatabase(student) + .first() + .filter { !it.isNotified && it.date >= LocalDate.now() } + .let { + if (it.isNotEmpty()) { + newSchoolAnnouncementNotification.notify(it, student) + } + + schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement -> + schoolAnnouncement.isNotified = true + }) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt index 5a7a41d8..e7c72bf0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt @@ -2,13 +2,14 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.teacher.TeacherRepository -import io.reactivex.Completable +import io.github.wulkanowy.data.repositories.TeacherRepository +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { - override fun create(student: Student, semester: Semester): Completable { - return teacherRepository.getTeachers(student, semester, true).ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + teacherRepository.getTeachers(student, semester, true).waitForResult() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 0990ed67..2d10d925 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -2,17 +2,44 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.monday -import io.reactivex.Completable -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import java.time.LocalDate.now import javax.inject.Inject -class TimetableWork @Inject constructor(private val timetableRepository: TimetableRepository) : Work { +class TimetableWork @Inject constructor( + private val timetableRepository: TimetableRepository, + private val changeTimetableNotification: ChangeTimetableNotification, +) : Work { - override fun create(student: Student, semester: Semester): Completable { - return timetableRepository.getTimetable(student, semester, now().monday, now().friday, true) - .ignoreElement() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().nextOrSameSchoolDay + val endDate = startDate.plusDays(7) + + timetableRepository.getTimetable( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + notify = notify, + ) + .waitForResult() + + timetableRepository.getTimetableFromDatabase( + semester = semester, + start = startDate, + end = endDate, + ) + .filterNot { it.isNotified } + .let { + if (it.isNotEmpty()) changeTimetableNotification.notify(it, student) + + timetableRepository.updateTimetable(it.onEach { timetable -> + timetable.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt index 1601a103..1c0214cd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt @@ -2,10 +2,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.reactivex.Completable interface Work { - fun create(student: Student, semester: Semester): Completable + suspend fun doWork(student: Student, semester: Semester, notify: Boolean) } - diff --git a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt index e5ccf963..ffdb07ec 100644 --- a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt +++ b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt @@ -2,16 +2,17 @@ package io.github.wulkanowy.services.widgets import android.content.Intent import android.widget.RemoteViewsService -import dagger.android.AndroidInjection +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetFactory -import io.github.wulkanowy.utils.SchedulersProvider +import timber.log.Timber import javax.inject.Inject +@AndroidEntryPoint class TimetableWidgetService : RemoteViewsService() { @Inject @@ -23,17 +24,22 @@ class TimetableWidgetService : RemoteViewsService() { @Inject lateinit var semesterRepo: SemesterRepository - @Inject - lateinit var prefRepository: PreferencesRepository - @Inject lateinit var sharedPref: SharedPrefProvider @Inject - lateinit var schedulers: SchedulersProvider + lateinit var prefRepository: PreferencesRepository override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { - AndroidInjection.inject(this) - return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, prefRepository, sharedPref, schedulers, applicationContext, intent) + Timber.d("TimetableWidgetFactory created") + return TimetableWidgetFactory( + timetableRepository = timetableRepo, + studentRepository = studentRepo, + semesterRepository = semesterRepo, + sharedPref = sharedPref, + prefRepository = prefRepository, + context = applicationContext, + intent = intent, + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index ee74832f..922c3536 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -1,32 +1,31 @@ package io.github.wulkanowy.ui.base import android.app.ActivityManager -import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP +import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AppCompatDelegate +import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG -import dagger.android.AndroidInjection -import dagger.android.DispatchingAndroidInjector -import dagger.android.HasAndroidInjector import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.auth.AuthDialog +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.openInternetBrowser +import timber.log.Timber +import java.time.Instant import javax.inject.Inject -abstract class BaseActivity> : AppCompatActivity(), BaseView, - HasAndroidInjector { +abstract class BaseActivity, VB : ViewBinding> : + AppCompatActivity(), BaseView { - @Inject - lateinit var androidInjector: DispatchingAndroidInjector + protected var binding: VB by lifecycleAwareVariable() @Inject lateinit var fragmentLifecycleLogger: FragmentLifecycleLogger @@ -36,18 +35,29 @@ abstract class BaseActivity> : AppCompatActivity protected var messageContainer: View? = null + protected var messageAnchor: View? = null + abstract var presenter: T - public override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) + private var lastDialogOpenTime = mutableMapOf() + + override fun onCreate(savedInstanceState: Bundle?) { + inject() themeManager.applyActivityTheme(this) super.onCreate(savedInstanceState) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true) - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) + applyCustomTaskDescription() + } - if (SDK_INT >= LOLLIPOP) { - @Suppress("DEPRECATION") - setTaskDescription(ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))) + @Suppress("DEPRECATION") + private fun applyCustomTaskDescription() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return + try { + val newColor = getThemeAttrColor(R.attr.colorSurface) + val taskDescription = ActivityManager.TaskDescription(null, null, newColor) + setTaskDescription(taskDescription) + } catch (e: Exception) { + Timber.e(e) } } @@ -55,6 +65,7 @@ abstract class BaseActivity> : AppCompatActivity if (messageContainer != null) { Snackbar.make(messageContainer!!, text, LENGTH_LONG) .setAction(R.string.all_details) { showErrorDetailsDialog(error) } + .apply { messageAnchor?.let { anchorView = it } } .show() } else showMessage(text) } @@ -64,22 +75,55 @@ abstract class BaseActivity> : AppCompatActivity } override fun showMessage(text: String) { - if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() - else Toast.makeText(this, text, Toast.LENGTH_LONG).show() + if (messageContainer != null) { + Snackbar.make(messageContainer!!, text, LENGTH_LONG) + .apply { messageAnchor?.let { anchorView = it } } + .show() + } else Toast.makeText(this, text, Toast.LENGTH_LONG).show() } - override fun showExpiredDialog() { - AlertDialog.Builder(this) - .setTitle(R.string.main_session_expired) - .setMessage(R.string.main_session_relogin) - .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() } + override fun showExpiredCredentialsDialog() { + if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.main_expired_credentials_title) + .setMessage(R.string.main_expired_credentials_description) + .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmExpiredCredentialsSelected() } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() } + override fun onCaptchaVerificationRequired(url: String?) { + CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog") + } + + override fun showDecryptionFailedDialog() { + if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.main_session_expired) + .setMessage(R.string.main_session_relogin) + .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmDecryptionFailedSelected() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + + override fun showAuthDialog() { + AuthDialog.newInstance().show(supportFragmentManager, "auth_dialog") + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + messageContainer?.let { + Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) + .setAction(R.string.all_change) { openInternetBrowser(redirectUrl) } + .apply { messageAnchor?.let { anchorView = it } } + .show() + } + } + override fun openClearLoginView() { - startActivity(LoginActivity.getStartIntent(this) - .apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) }) + startActivity(LoginActivity.getStartIntent(this)) + finishAffinity() } override fun onDestroy() { @@ -88,5 +132,25 @@ abstract class BaseActivity> : AppCompatActivity presenter.onDetachView() } - override fun androidInjector() = androidInjector + //https://github.com/google/dagger/releases/tag/dagger-2.33 + protected open fun inject() { + throw UnsupportedOperationException() + } + + private fun shouldShowDialog(name: String): Boolean { + val lastOpenTime = lastDialogOpenTime[name] + val now = Instant.now() + + if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) { + Timber.i("Dialog $name was shown less than a second ago. Skip") + return false + } + lastDialogOpenTime[name] = Instant.now() + return true + } + + companion object { + private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials" + private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed" + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index fdc46371..e63887b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -1,9 +1,23 @@ package io.github.wulkanowy.ui.base +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.Toast -import dagger.android.support.DaggerAppCompatDialogFragment +import androidx.fragment.app.DialogFragment +import androidx.viewbinding.ViewBinding +import com.google.android.material.elevation.SurfaceColors +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.lifecycleAwareVariable +import javax.inject.Inject -abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView { +abstract class BaseDialogFragment : DialogFragment(), BaseView { + + @Inject + lateinit var analyticsHelper: AnalyticsHelper + + protected var binding: VB by lifecycleAwareVariable() override fun showError(text: String, error: Throwable) { showMessage(text) @@ -13,15 +27,52 @@ abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView { Toast.makeText(context, text, Toast.LENGTH_LONG).show() } - override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + view.setBackgroundColor(SurfaceColors.SURFACE_3.getColor(requireContext())) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = binding.root + + override fun onResume() { + super.onResume() + analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName) + } + + override fun onPause() { + super.onPause() + analyticsHelper.popCurrentScreen(this::class.simpleName) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt new file mode 100644 index 00000000..eee4625c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseExpandableAdapter.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.base + +import android.util.DisplayMetrics +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.max +import kotlin.math.min + +abstract class BaseExpandableAdapter : RecyclerView.Adapter() { + + companion object { + private const val MILLISECONDS_PER_INCH = 100f + private const val AUTO_SCROLL_DELAY = 150L + } + + private var recyclerView: RecyclerView? = null + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + this.recyclerView = recyclerView + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + this.recyclerView = null + } + + // original: https://github.com/davideas/FlexibleAdapter/blob/5.1.0/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java#L4984-L5011 + protected fun scrollToHeaderWithSubItems(position: Int, subItemsCount: Int) { + val layoutManager = recyclerView!!.layoutManager as LinearLayoutManager + val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition() + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + val itemsToShow = position + subItemsCount - lastVisibleItem + if (itemsToShow > 0) { + val scrollMax: Int = position - firstVisibleItem + val scrollMin = max(0, position + subItemsCount - lastVisibleItem) + val scrollBy = min(scrollMax, scrollMin) + val scrollTo = firstVisibleItem + scrollBy + scrollToPosition(scrollTo) + } else if (position < firstVisibleItem) { + scrollToPosition(position) + } + } + + private fun scrollToPosition(position: Int) { + recyclerView?.run { + postDelayed({ + layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) { + override fun getVerticalSnapPreference() = SNAP_TO_START + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) = MILLISECONDS_PER_INCH / displayMetrics.densityDpi + }.apply { + targetPosition = position + }) + }, AUTO_SCROLL_DELAY) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index 2f5878d0..ba346131 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -1,12 +1,18 @@ package io.github.wulkanowy.ui.base import android.view.View +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG -import dagger.android.support.DaggerFragment import io.github.wulkanowy.R +import io.github.wulkanowy.utils.lifecycleAwareVariable -abstract class BaseFragment : DaggerFragment(), BaseView { +abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), + BaseView { + + protected var binding: VB by lifecycleAwareVariable() protected var messageContainer: View? = null @@ -16,7 +22,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView { .setAction(R.string.all_details) { if (isAdded) showErrorDetailsDialog(error) } .show() } else { - (activity as? BaseActivity<*>)?.showError(text, error) + (activity as? BaseActivity<*, *>)?.showError(text, error) } } @@ -28,15 +34,31 @@ abstract class BaseFragment : DaggerFragment(), BaseView { if (messageContainer != null) { Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() } else { - (activity as? BaseActivity<*>)?.showMessage(text) + (activity as? BaseActivity<*, *>)?.showMessage(text) } } - override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt index 5f8bf417..6bca87f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt @@ -2,31 +2,33 @@ package io.github.wulkanowy.ui.base import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator -class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) : - FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { +class BaseFragmentPagerAdapter( + private val fragmentManager: FragmentManager, + private val pagesCount: Int, + lifecycle: Lifecycle, +) : FragmentStateAdapter(fragmentManager, lifecycle), TabLayoutMediator.TabConfigurationStrategy { - private val pages = mutableMapOf() + lateinit var itemFactory: (position: Int) -> Fragment + + var titleFactory: (position: Int) -> String? = { "" } var containerId = 0 fun getFragmentInstance(position: Int): Fragment? { require(containerId != 0) { "Container id is 0" } - return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position") + return fragmentManager.findFragmentByTag("f$position") } - fun addFragments(fragments: List) { - fragments.forEach { pages[it] = null } + override fun createFragment(position: Int): Fragment = itemFactory(position) + + override fun getItemCount() = pagesCount + + override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { + tab.text = titleFactory(position) } - - fun addFragmentsWithTitle(pages: Map) { - this.pages.putAll(pages) - } - - override fun getItem(position: Int) = pages.keys.elementAt(position) - - override fun getCount() = pages.size - - override fun getPageTitle(position: Int) = pages.values.elementAt(position) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 9f0b4047..d4cb20ca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,18 +1,26 @@ package io.github.wulkanowy.ui.base -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable -import io.reactivex.disposables.CompositeDisposable +import io.github.wulkanowy.data.repositories.StudentRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.launch import timber.log.Timber open class BasePresenter( protected val errorHandler: ErrorHandler, - protected val studentRepository: StudentRepository, - protected val schedulers: SchedulersProvider + protected val studentRepository: StudentRepository ) { + private val job = SupervisorJob() - val disposable = CompositeDisposable() + protected val presenterScope = CoroutineScope(job + Dispatchers.Main) + + private val childrenJobs = mutableMapOf() var view: T? = null @@ -20,36 +28,73 @@ open class BasePresenter( this.view = view errorHandler.apply { showErrorMessage = view::showError - onSessionExpired = view::showExpiredDialog + onExpiredCredentials = view::showExpiredCredentialsDialog + onCaptchaVerificationRequired = view::onCaptchaVerificationRequired + onDecryptionFailed = view::showDecryptionFailedDialog onNoCurrentStudent = view::openClearLoginView + onPasswordChangeRequired = view::showChangePasswordSnackbar + onAuthorizationRequired = view::showAuthDialog } } - fun onExpiredLoginSelected() { - Timber.i("Attempt to switch the student after the session expires") - disposable.add(studentRepository.getCurrentStudent(false) - .flatMapCompletable { studentRepository.logoutStudent(it) } - .andThen(studentRepository.getSavedStudents(false)) - .flatMapCompletable { - if (it.isNotEmpty()) { + fun onConfirmDecryptionFailedSelected() { + Timber.i("Attempt to clear all data") + + presenterScope.launch { + runCatching { studentRepository.clearAll() } + .onFailure { + Timber.i("Clear data result: An exception occurred") + errorHandler.dispatch(it) + } + .onSuccess { + Timber.i("Clear data result: Open login view") + view?.openClearLoginView() + } + } + } + + fun onConfirmExpiredCredentialsSelected() { + Timber.i("Attempt to delete students associated with the account and switch to new student") + + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(false) + studentRepository.deleteStudentsAssociatedWithAccount(student) + + val students = studentRepository.getSavedStudents(false) + if (students.isNotEmpty()) { Timber.i("Switching current student") - studentRepository.switchStudent(it[0]) - } else Completable.complete() + studentRepository.switchStudent(students[0]) + } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Switch student result: Open login view") - view?.openClearLoginView() - }, { - Timber.i("Switch student result: An exception occurred") - errorHandler.dispatch(it) - })) + .onFailure { + Timber.i("Delete students result: An exception occurred") + errorHandler.dispatch(it) + } + .onSuccess { + Timber.i("Delete students result: Open login view") + view?.openClearLoginView() + } + } + } + + fun Flow.launch(individualJobTag: String = "load"): Job { + childrenJobs[individualJobTag]?.cancel() + val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope) + childrenJobs[individualJobTag] = job + Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job") + return job + } + + fun cancelJobs(vararg names: String) { + names.forEach { + childrenJobs[it]?.cancel() + } } open fun onDetachView() { - view = null - disposable.clear() + job.cancelChildren() errorHandler.clear() + view = null } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index 0f4df92c..88d5754f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -6,9 +6,17 @@ interface BaseView { fun showMessage(text: String) - fun showExpiredDialog() + fun showExpiredCredentialsDialog() + + fun onCaptchaVerificationRequired(url: String?) + + fun showDecryptionFailedDialog() + + fun showAuthDialog() fun openClearLoginView() fun showErrorDetailsDialog(error: Throwable) + + fun showChangePasswordSnackbar(redirectUrl: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index 37418113..679d904a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -1,87 +1,91 @@ package io.github.wulkanowy.ui.base +import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.widget.HorizontalScrollView import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.appcompat.app.AlertDialog import androidx.core.content.getSystemService +import androidx.core.os.bundleOf +import androidx.core.view.isGone +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.sdk.exception.FeatureDisabledException -import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException -import io.github.wulkanowy.sdk.exception.ServiceUnavailableException -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.getString -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.dialog_error.* -import java.io.PrintWriter -import java.io.StringWriter -import java.net.SocketTimeoutException -import java.net.UnknownHostException +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.DialogErrorBinding +import io.github.wulkanowy.utils.* import javax.inject.Inject -class ErrorDialog : BaseDialogFragment() { - - private lateinit var error: Throwable +@AndroidEntryPoint +class ErrorDialog : BaseDialogFragment() { @Inject lateinit var appInfo: AppInfo - companion object { - private const val ARGUMENT_KEY = "Data" + @Inject + lateinit var preferencesRepository: PreferencesRepository - fun newInstance(error: Throwable): ErrorDialog { - return ErrorDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) } - } + private lateinit var error: Throwable + + companion object { + private const val ARGUMENT_KEY = "error" + + fun newInstance(error: Throwable) = ErrorDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to error) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - error = getSerializable(ARGUMENT_KEY) as Throwable + error = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext()).apply { + val errorStacktrace = error.stackTraceToString() + setTitle(R.string.all_details) + setView(DialogErrorBinding.inflate(layoutInflater).apply { binding = this }.root) + setNeutralButton(R.string.about_feedback) { _, _ -> + openConfirmDialog { openEmailClient(errorStacktrace) } + } + setNegativeButton(android.R.string.cancel) { _, _ -> } + setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) } + }.create().apply { + setOnShowListener { + getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() + } } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_error, container, false) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { + errorDialogHumanizedMessage.text = resources.getErrorString(error) + errorDialogErrorMessage.text = error.localizedMessage + errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() + errorDialogContent.text = error.stackTraceToString() + .replace(": ${error.localizedMessage}", "") + } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + private fun copyErrorToClipboard(errorStacktrace: String) { + val clip = ClipData.newPlainText("Error details", errorStacktrace) + requireActivity().getSystemService()?.setPrimaryClip(clip) + Toast.makeText(requireContext(), R.string.all_copied, LENGTH_LONG).show() + } - val stringWriter = StringWriter().apply { - error.printStackTrace(PrintWriter(this)) - } - - errorDialogContent.text = stringWriter.toString() - with(errorDialogHorizontalScroll) { - post { fullScroll(HorizontalScrollView.FOCUS_LEFT) } - } - errorDialogCopy.setOnClickListener { - val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString()) - activity?.getSystemService()?.setPrimaryClip(clip) - - Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() - } - errorDialogCancel.setOnClickListener { dismiss() } - errorDialogReport.setOnClickListener { openEmailClient(stringWriter.toString()) } - errorDialogMessage.text = resources.getString(error) - errorDialogReport.isEnabled = when (error) { - is UnknownHostException, - is SocketTimeoutException, - is ServiceUnavailableException, - is FeatureDisabledException, - is FeatureNotAvailableException -> false - else -> true - } + private fun openConfirmDialog(callback: () -> Unit) { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.dialog_error_check_update) + .setMessage(R.string.dialog_error_check_update_message) + .setNeutralButton(R.string.about_feedback) { _, _ -> callback() } + .setPositiveButton(R.string.dialog_error_check_update) { _, _ -> + requireContext().openAppInMarket(::showMessage) + } + .show() } private fun openEmailClient(content: String) { @@ -89,11 +93,18 @@ class ErrorDialog : BaseDialogFragment() { chooserTitle = getString(R.string.about_feedback), email = "wulkanowyinc@gmail.com", subject = "Zgłoszenie błędu", - body = requireContext().getString(R.string.about_feedback_template, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName + body = requireContext().getString( + R.string.about_feedback_template, + "${appInfo.systemManufacturer} ${appInfo.systemModel}", + appInfo.systemVersion.toString(), + "${appInfo.versionName}-${appInfo.buildFlavor}", + preferencesRepository.installationId, ) + "\n" + content, onActivityNotFound = { - requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage) + requireContext().openInternetBrowser( + "https://github.com/wulkanowy/wulkanowy/issues", + ::showMessage + ) } ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index f746b36f..00a2ab22 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -1,39 +1,60 @@ package io.github.wulkanowy.ui.base -import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.sdk.exception.BadCredentialsException -import io.github.wulkanowy.utils.getString +import io.github.wulkanowy.data.repositories.NoAuthorizationException +import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException +import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException +import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException +import io.github.wulkanowy.utils.getErrorString import io.github.wulkanowy.utils.security.ScramblerException import timber.log.Timber import javax.inject.Inject -open class ErrorHandler @Inject constructor(protected val resources: Resources, private val chuckerCollector: ChuckerCollector) { +open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) { var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> } - var onSessionExpired: () -> Unit = {} + var onExpiredCredentials: () -> Unit = {} + + var onDecryptionFailed: () -> Unit = {} var onNoCurrentStudent: () -> Unit = {} + var onPasswordChangeRequired: (String) -> Unit = {} + + var onAuthorizationRequired: () -> Unit = {} + + var onCaptchaVerificationRequired: (url: String?) -> Unit = {} + fun dispatch(error: Throwable) { - chuckerCollector.onError(error.javaClass.simpleName, error) Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) } protected open fun proceed(error: Throwable) { + showDefaultMessage(error) when (error) { - is ScramblerException, is BadCredentialsException -> onSessionExpired() + is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) + is ScramblerException -> onDecryptionFailed() + is BadCredentialsException -> onExpiredCredentials() is NoCurrentStudentException -> onNoCurrentStudent() - else -> showErrorMessage(resources.getString(error), error) + is NoAuthorizationException -> onAuthorizationRequired() + is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl) } } + fun showDefaultMessage(error: Throwable) { + showErrorMessage(context.resources.getErrorString(error), error) + } + open fun clear() { showErrorMessage = { _, _ -> } - onSessionExpired = {} + onExpiredCredentials = {} + onDecryptionFailed = {} onNoCurrentStudent = {} + onPasswordChangeRequired = {} + onAuthorizationRequired = {} } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt index 50134861..f42f315c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt @@ -1,13 +1,19 @@ package io.github.wulkanowy.ui.base +import android.content.pm.PackageInfo +import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_ACTIVITIES +import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES +import com.google.android.material.color.DynamicColors import io.github.wulkanowy.R -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.enums.AppTheme +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity import javax.inject.Inject import javax.inject.Singleton @@ -17,24 +23,44 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer fun applyActivityTheme(activity: AppCompatActivity) { if (isThemeApplicable(activity)) { applyDefaultTheme() - if (preferencesRepository.appTheme == "black") activity.setTheme(R.style.WulkanowyTheme_Black) + if (preferencesRepository.appTheme == AppTheme.BLACK) { + when (activity) { + is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black) + is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black) + } + } + } else if (activity is TimetableWidgetConfigureActivity || activity is LuckyNumberWidgetConfigureActivity) { + DynamicColors.applyToActivityIfAvailable(activity) } } fun applyDefaultTheme() { AppCompatDelegate.setDefaultNightMode( - when (val theme = preferencesRepository.appTheme) { - "light" -> MODE_NIGHT_NO - "dark", "black" -> MODE_NIGHT_YES - "system" -> MODE_NIGHT_FOLLOW_SYSTEM - else -> throw IllegalArgumentException("Wrong theme: $theme") + when (preferencesRepository.appTheme) { + AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + AppTheme.DARK, AppTheme.BLACK -> AppCompatDelegate.MODE_NIGHT_YES + AppTheme.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } ) } - private fun isThemeApplicable(activity: AppCompatActivity): Boolean { - return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES) - .activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme - .let { it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar } + private fun isThemeApplicable(activity: AppCompatActivity): Boolean = + getPackageInfo(activity) + .activities + .singleOrNull { it.name == activity::class.java.canonicalName } + ?.theme + .let { + it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar + || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black + } + + @Suppress("DEPRECATION") + private fun getPackageInfo(activity: AppCompatActivity): PackageInfo { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + activity.packageManager.getPackageInfo( + activity.packageName, + PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong()) + ) + } else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt new file mode 100644 index 00000000..a43aaffb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.ui.base + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.utils.createNameInitialsDrawable +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nickOrName +import javax.inject.Inject + +class WidgetConfigureAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var selectedId = -1L + + var onClickListener: (Student) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val (student, semesters) = items[position] + val semester = semesters.maxByOrNull { it.semesterId } + val context = holder.binding.root.context + val checkBackgroundColor = context.getThemeAttrColor(R.attr.colorSurface) + val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor) + val isDuplicatedStudent = items.filter { + val studentToCompare = it.student + + studentToCompare.studentId == student.studentId + && studentToCompare.schoolSymbol == student.schoolSymbol + && studentToCompare.symbol == student.symbol + }.size > 1 + + with(holder.binding) { + accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}" + accountItemSchool.text = student.schoolName + accountItemImage.setImageDrawable(avatar) + + with(accountItemAccountType) { + setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student) + isVisible = isDuplicatedStudent + } + + with(accountItemCheck) { + isVisible = student.id == selectedId + borderColor = checkBackgroundColor + circleColor = checkBackgroundColor + } + + root.setOnClickListener { onClickListener(student) } + } + } + + class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt new file mode 100644 index 00000000..f0969fac --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -0,0 +1,152 @@ +package io.github.wulkanowy.ui.modules + +import androidx.fragment.app.Fragment +import io.github.wulkanowy.data.serializers.LocalDateSerializer +import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment +import io.github.wulkanowy.ui.modules.conference.ConferenceFragment +import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment +import io.github.wulkanowy.ui.modules.exam.ExamFragment +import io.github.wulkanowy.ui.modules.grade.GradeFragment +import io.github.wulkanowy.ui.modules.homework.HomeworkFragment +import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment +import io.github.wulkanowy.ui.modules.message.MessageFragment +import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment +import io.github.wulkanowy.ui.modules.more.MoreFragment +import io.github.wulkanowy.ui.modules.note.NoteFragment +import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment +import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment +import io.github.wulkanowy.ui.modules.settings.SettingsFragment +import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import kotlinx.serialization.Serializable +import java.time.LocalDate + +@Serializable +sealed class Destination { + + /* + Type in children classes have to be as getter to avoid null in enums + https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time + */ + abstract val destinationType: Type + + abstract val destinationFragment: Fragment + + enum class Type(val defaultDestination: Destination) { + DASHBOARD(Dashboard), + GRADE(Grade), + ATTENDANCE(Attendance), + EXAM(Exam), + TIMETABLE(Timetable()), + HOMEWORK(Homework), + NOTE(Note), + CONFERENCE(Conference), + SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), + SCHOOL_AND_TEACHERS(SchoolAndTeachers), + LUCKY_NUMBER(LuckyNumber), + LUCKY_NUMBER_HISTORY(LuckyNumberHistory), + MORE(More), + MESSAGE(Message), + MOBILE_DEVICE(MobileDevice), + SETTINGS(Settings); + } + + @Serializable + object Dashboard : Destination() { + override val destinationType get() = Type.DASHBOARD + override val destinationFragment get() = DashboardFragment.newInstance() + } + + @Serializable + object Grade : Destination() { + override val destinationType get() = Type.GRADE + override val destinationFragment get() = GradeFragment.newInstance() + } + + @Serializable + object Attendance : Destination() { + override val destinationType get() = Type.ATTENDANCE + override val destinationFragment get() = AttendanceFragment.newInstance() + } + + @Serializable + object Exam : Destination() { + override val destinationType get() = Type.EXAM + override val destinationFragment get() = ExamFragment.newInstance() + } + + @Serializable + data class Timetable( + @Serializable(with = LocalDateSerializer::class) + private val date: LocalDate? = null + ) : Destination() { + override val destinationType get() = Type.TIMETABLE + override val destinationFragment get() = TimetableFragment.newInstance(date) + } + + @Serializable + object Homework : Destination() { + override val destinationType get() = Type.HOMEWORK + override val destinationFragment get() = HomeworkFragment.newInstance() + } + + @Serializable + object Note : Destination() { + override val destinationType get() = Type.NOTE + override val destinationFragment get() = NoteFragment.newInstance() + } + + @Serializable + object Conference : Destination() { + override val destinationType get() = Type.CONFERENCE + override val destinationFragment get() = ConferenceFragment.newInstance() + } + + @Serializable + object SchoolAnnouncement : Destination() { + override val destinationType get() = Type.SCHOOL_ANNOUNCEMENT + override val destinationFragment get() = SchoolAnnouncementFragment.newInstance() + } + + @Serializable + object SchoolAndTeachers : Destination() { + override val destinationType get() = Type.SCHOOL_AND_TEACHERS + override val destinationFragment get() = SchoolAndTeachersFragment.newInstance() + } + + @Serializable + object LuckyNumber : Destination() { + override val destinationType get() = Type.LUCKY_NUMBER + override val destinationFragment get() = LuckyNumberFragment.newInstance() + } + + @Serializable + object LuckyNumberHistory : Destination() { + override val destinationType get() = Type.LUCKY_NUMBER_HISTORY + override val destinationFragment get() = LuckyNumberHistoryFragment.newInstance() + } + + @Serializable + object More : Destination() { + override val destinationType get() = Type.MORE + override val destinationFragment get() = MoreFragment.newInstance() + } + + @Serializable + object Message : Destination() { + override val destinationType get() = Type.MESSAGE + override val destinationFragment get() = MessageFragment.newInstance() + } + + @Serializable + object MobileDevice : Destination() { + override val destinationType get() = Type.MOBILE_DEVICE + override val destinationFragment get() = MobileDeviceFragment.newInstance() + } + + @Serializable + object Settings : Destination() { + override val destinationType get() = Type.SETTINGS + override val destinationFragment get() = SettingsFragment.newInstance() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt new file mode 100644 index 00000000..cd2dc210 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutAdapter.kt @@ -0,0 +1,90 @@ +package io.github.wulkanowy.ui.modules.about + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.content.res.ResourcesCompat +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.ItemAboutBinding +import io.github.wulkanowy.databinding.ScrollableHeaderAboutBinding +import javax.inject.Inject + +class AboutAdapter @Inject constructor( + private val preferencesRepository: PreferencesRepository +) : RecyclerView.Adapter() { + + private var developerModeClicks = 0 + private enum class ViewType(val id: Int) { + ITEM_HEADER(1), + ITEM_ELEMENT(2) + } + + var items = emptyList>() + + var onClickListener: (name: String) -> Unit = {} + + override fun getItemCount() = items.size + 1 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.ITEM_HEADER.id + else -> ViewType.ITEM_ELEMENT.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.ITEM_HEADER.id -> HeaderViewHolder(ScrollableHeaderAboutBinding.inflate(inflater, parent, false)) + ViewType.ITEM_ELEMENT.id -> ItemViewHolder(ItemAboutBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 1) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderAboutBinding) { + with(binding.aboutScrollableHeaderIcon) { + setOnClickListener { + if (++developerModeClicks == 5 && !preferencesRepository.developerMode) { + preferencesRepository.developerMode = true + developerModeClicks = 0 + + Toast.makeText( + context, + "done!", + Toast.LENGTH_SHORT + ).show() + } + } + + setImageDrawable(ResourcesCompat.getDrawableForDensity( + context.resources, context.applicationInfo.icon, 640, null) + ) + } + } + + private fun bindItemViewHolder(binding: ItemAboutBinding, position: Int) { + val (title, summary, image) = items[position] + + with(binding) { + aboutItemImage.setImageDrawable(image) + aboutItemTitle.text = title + aboutItemSummary.text = summary + + root.setOnClickListener { onClickListener(title) } + } + } + + private class HeaderViewHolder(val binding: ScrollableHeaderAboutBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemAboutBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 5a32ac83..d7f39e30 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -2,76 +2,130 @@ package io.github.wulkanowy.ui.modules.about import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentAboutBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment import io.github.wulkanowy.ui.modules.about.license.LicenseFragment -import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment +import io.github.wulkanowy.ui.modules.debug.DebugFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_about.* +import io.github.wulkanowy.utils.* +import java.time.Instant import javax.inject.Inject -class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { +@AndroidEntryPoint +class AboutFragment : BaseFragment(R.layout.fragment_about), AboutView, + MainView.TitledView { @Inject lateinit var presenter: AboutPresenter @Inject - lateinit var aboutAdapter: FlexibleAdapter> + lateinit var aboutAdapter: AboutAdapter @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + override val versionRes: Triple? get() = context?.run { - Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about)) + val buildTimestamp = + Instant.ofEpochMilli(appInfo.buildTimestamp).toFormattedString("yyyy-MM-dd") + val versionSignature = + "${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp" + Triple( + getString(R.string.about_version), + versionSignature, + getCompatDrawable(R.drawable.ic_all_about) + ) } override val creatorsRes: Triple? get() = context?.run { - Triple(getString(R.string.about_contributor), getString(R.string.about_contributor_summary), getCompatDrawable(R.drawable.ic_about_creator)) + Triple( + getString(R.string.about_contributor), + getString(R.string.about_contributor_summary), + getCompatDrawable(R.drawable.ic_about_creator) + ) } override val feedbackRes: Triple? get() = context?.run { - Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback)) + Triple( + getString(R.string.about_feedback), + getString(R.string.about_feedback_summary), + getCompatDrawable(R.drawable.ic_about_feedback) + ) } override val faqRes: Triple? get() = context?.run { - Triple(getString(R.string.about_faq), getString(R.string.about_faq_summary), getCompatDrawable(R.drawable.ic_about_faq)) + Triple( + getString(R.string.about_faq), + getString(R.string.about_faq_summary), + getCompatDrawable(R.drawable.ic_about_faq) + ) } override val discordRes: Triple? get() = context?.run { - Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord)) + Triple( + getString(R.string.about_discord), + getString(R.string.about_discord_summary), + getCompatDrawable(R.drawable.ic_about_discord) + ) + } + + override val facebookRes: Triple? + get() = context?.run { + Triple( + getString(R.string.about_facebook), + getString(R.string.about_facebook_summary), + getCompatDrawable(R.drawable.ic_about_facebook) + ) + } + + override val twitterRes: Triple? + get() = context?.run { + Triple( + getString(R.string.about_twitter), + getString(R.string.about_twitter_summary), + getCompatDrawable(R.drawable.ic_about_twitter) + ) } override val homepageRes: Triple? get() = context?.run { - Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage)) + Triple( + getString(R.string.about_homepage), + getString(R.string.about_homepage_summary), + getCompatDrawable(R.drawable.ic_all_home) + ) } override val licensesRes: Triple? get() = context?.run { - Triple(getString(R.string.about_licenses), getString(R.string.about_licenses_summary), getCompatDrawable(R.drawable.ic_about_licenses)) + Triple( + getString(R.string.about_licenses), + getString(R.string.about_licenses_summary), + getCompatDrawable(R.drawable.ic_about_licenses) + ) } override val privacyRes: Triple? get() = context?.run { - Triple(getString(R.string.about_privacy), getString(R.string.about_privacy_summary), getCompatDrawable(R.drawable.ic_about_privacy)) + Triple( + getString(R.string.about_privacy), + getString(R.string.about_privacy_summary), + getCompatDrawable(R.drawable.ic_about_privacy) + ) } override val titleStringId get() = R.string.about_title @@ -80,40 +134,48 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { fun newInstance() = AboutFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_about, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding = FragmentAboutBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - aboutAdapter.setOnItemClickListener(presenter::onItemSelected) + aboutAdapter.onClickListener = presenter::onItemSelected - with(aboutRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.aboutRecycler) { + layoutManager = LinearLayoutManager(context) adapter = aboutAdapter } } - override fun updateData(header: AboutScrollableHeader, items: List) { + override fun updateData(data: List>) { with(aboutAdapter) { - removeAllScrollableHeaders() - addScrollableHeader(header) - updateDataSet(items) + items = data + notifyDataSetChanged() } } - override fun openLogViewer() { - if (appInfo.isDebug) (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance()) + override fun openAppInMarket() { + context?.openAppInMarket(::showMessage) + } + + override fun openDebugScreen() { + (activity as? MainActivity)?.pushView(DebugFragment.newInstance()) } override fun openDiscordInvite() { context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) } + override fun openFacebookPage() { + context?.openInternetBrowser("https://www.facebook.com/wulkanowy", ::showMessage) + } + + override fun openTwitterPage() { + context?.openInternetBrowser("https://twitter.com/wulkanowy", ::showMessage) + } + override fun openHomepage() { context?.openInternetBrowser("https://wulkanowy.github.io/", ::showMessage) } @@ -123,17 +185,27 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { chooserTitle = getString(R.string.about_feedback), email = "wulkanowyinc@gmail.com", subject = "Zgłoszenie błędu", - body = requireContext().getString(R.string.about_feedback_template, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName + body = getString( + R.string.about_feedback_template, + "${appInfo.systemManufacturer} ${appInfo.systemModel}", + appInfo.systemVersion.toString(), + "${appInfo.versionName}-${appInfo.buildFlavor}", + preferencesRepository.installationId, ), onActivityNotFound = { - requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage) + requireContext().openInternetBrowser( + "https://github.com/wulkanowy/wulkanowy/issues", + ::showMessage + ) } ) } override fun openFaqPage() { - context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania", ::showMessage) + context?.openInternetBrowser( + "https://wulkanowy.github.io/czesto-zadawane-pytania", + ::showMessage + ) } override fun openLicenses() { @@ -145,7 +217,10 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView { } override fun openPrivacyPolicy() { - context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage) + context?.openInternetBrowser( + "https://wulkanowy.github.io/polityka-prywatnosci.html", + ::showMessage + ) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt deleted file mode 100644 index 29f1cd8c..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutItem.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.wulkanowy.ui.modules.about - -import android.graphics.drawable.Drawable -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_about.* - -class AboutItem( - val title: String, - private val summary: String, - private val image: Drawable? -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_about - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - aboutItemImage.setImageDrawable(image) - aboutItemTitle.text = title - aboutItemSummary.text = summary - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AboutItem - - if (title != other.title) return false - if (summary != other.summary) return false - if (image != other.image) return false - - return true - } - - override fun hashCode(): Int { - var result = title.hashCode() - result = 31 * result + summary.hashCode() - result = 31 * result + (image?.hashCode() ?: 0) - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index 7e740b32..55274934 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -1,20 +1,19 @@ package io.github.wulkanowy.ui.modules.about -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import timber.log.Timber import javax.inject.Inject class AboutPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val appInfo: AppInfo, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: AboutView) { super.onAttachView(view) @@ -23,14 +22,14 @@ class AboutPresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is AboutItem) return + fun onItemSelected(name: String) { view?.run { - when (item.title) { + when (name) { versionRes?.first -> { - Timber.i("Opening log viewer") - openLogViewer() - analytics.logEvent("about_open", "name" to "log_viewer") + Timber.i("Opening debug screen") + if (appInfo.isDebug) openDebugScreen() + else openAppInMarket() + analytics.logEvent("about_open", "name" to "debug_screen") } feedbackRes?.first -> { Timber.i("Opening email client") @@ -47,6 +46,16 @@ class AboutPresenter @Inject constructor( openDiscordInvite() analytics.logEvent("about_open", "name" to "discord") } + facebookRes?.first -> { + Timber.i("Opening facebook") + openFacebookPage() + analytics.logEvent("about_open", "name" to "facebook") + } + twitterRes?.first -> { + Timber.i("Opening twitter") + openTwitterPage() + analytics.logEvent("about_open", "name" to "twitter") + } homepageRes?.first -> { Timber.i("Opening homepage") openHomepage() @@ -73,15 +82,20 @@ class AboutPresenter @Inject constructor( private fun loadData() { view?.run { - updateData(AboutScrollableHeader(), listOfNotNull( - versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - creatorsRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, - privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) })) + updateData( + listOfNotNull( + versionRes, + creatorsRes, + feedbackRes, + faqRes, + discordRes, + facebookRes, + twitterRes, + homepageRes, + licensesRes, + privacyRes + ) + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt deleted file mode 100644 index 07bb4124..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutScrollableHeader.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.wulkanowy.ui.modules.about - -import android.view.View -import androidx.core.content.res.ResourcesCompat -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.scrollable_header_about.* - -class AboutScrollableHeader : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.scrollable_header_about - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - val context = contentView.context - val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null) - - aboutScrollableHeaderIcon.setImageDrawable(drawable) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - return true - } - - override fun hashCode() = javaClass.hashCode() - - class ViewHolder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt index 4bc0c3fe..5c286828 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt @@ -15,6 +15,10 @@ interface AboutView : BaseView { val discordRes: Triple? + val twitterRes: Triple? + + val facebookRes: Triple? + val homepageRes: Triple? val licensesRes: Triple? @@ -23,12 +27,18 @@ interface AboutView : BaseView { fun initView() - fun updateData(header: AboutScrollableHeader, items: List) + fun updateData(data: List>) - fun openLogViewer() + fun openAppInMarket() + + fun openDebugScreen() fun openDiscordInvite() + fun openFacebookPage() + + fun openTwitterPage() + fun openEmailClient() fun openFaqPage() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt new file mode 100644 index 00000000..b6755c6a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorAdapter.kt @@ -0,0 +1,41 @@ +package io.github.wulkanowy.ui.modules.about.contributor + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.RoundedCornersTransformation +import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.databinding.ItemContributorBinding +import javax.inject.Inject + +class ContributorAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (Contributor) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemContributorBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + creatorItemName.text = item.displayName + creatorItemAvatar.load("https://github.com/${item.githubUsername}.png") { + transformations(RoundedCornersTransformation(8f)) + crossfade(true) + } + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemContributorBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt index c181c3d3..5d7e076c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorFragment.kt @@ -1,30 +1,29 @@ package io.github.wulkanowy.ui.modules.about.contributor import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.databinding.FragmentContributorBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_creator.* import javax.inject.Inject -class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView { +@AndroidEntryPoint +class ContributorFragment : BaseFragment(R.layout.fragment_contributor), + ContributorView, MainView.TitledView { @Inject lateinit var presenter: ContributorPresenter @Inject - lateinit var creatorsAdapter: FlexibleAdapter> + lateinit var creatorsAdapter: ContributorAdapter override val titleStringId get() = R.string.contributors_title @@ -32,29 +31,27 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView fun newInstance() = ContributorFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_creator, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentContributorBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - with(creatorRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.creatorRecycler) { + layoutManager = LinearLayoutManager(context) adapter = creatorsAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } - creatorsAdapter.setOnItemClickListener(presenter::onItemSelected) - creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } + creatorsAdapter.onClickListener = presenter::onItemSelected + binding.creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } } - override fun updateData(data: List) { - creatorsAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(creatorsAdapter) { + items = data + notifyDataSetChanged() + } } override fun openUserGithubPage(username: String) { @@ -66,7 +63,7 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView } override fun showProgress(show: Boolean) { - creatorProgress.visibility = if (show) VISIBLE else GONE + binding.creatorProgress.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt deleted file mode 100644 index 844b5bd8..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorItem.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.contributor - -import android.view.View -import coil.api.load -import coil.transform.RoundedCornersTransformation -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.pojos.AppCreator -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_contributor.* - -class ContributorItem(val creator: AppCreator) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_contributor - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - creatorItemName.text = creator.displayName - - creatorItemAvatar.load("https://github.com/${creator.githubUsername}.png") { - transformations(RoundedCornersTransformation(8f)) - crossfade(true) - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ContributorItem - - if (creator != other.creator) return false - - return true - } - - override fun hashCode() = creator.hashCode() - - class ViewHolder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index 721b2500..126bb2b4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -1,19 +1,18 @@ package io.github.wulkanowy.ui.modules.about.contributor -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.pojos.Contributor +import io.github.wulkanowy.data.repositories.AppCreatorRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider import javax.inject.Inject class ContributorPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val appCreatorRepository: AppCreatorRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: ContributorView) { super.onAttachView(view) @@ -21,9 +20,8 @@ class ContributorPresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is ContributorItem) return - view?.openUserGithubPage(item.creator.githubUsername) + fun onItemSelected(contributor: Contributor) { + view?.openUserGithubPage(contributor.githubUsername) } fun onSeeMoreClick() { @@ -31,11 +29,11 @@ class ContributorPresenter @Inject constructor( } private fun loadData() { - disposable.add(appCreatorRepository.getAppCreators() - .map { it.map { creator -> ContributorItem(creator) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.showProgress(false) } - .subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) })) + resourceFlow { appCreatorRepository.getAppCreators() } + .onResourceLoading { view?.showProgress(true) } + .onResourceSuccess { view?.updateData(it) } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError { errorHandler.dispatch(it) } + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt index 18ec3a8e..8007e4e3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorView.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.about.contributor +import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.ui.base.BaseView interface ContributorView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun openUserGithubPage(username: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt new file mode 100644 index 00000000..adf4ca74 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.modules.about.license + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.aboutlibraries.entity.Library +import io.github.wulkanowy.databinding.ItemLicenseBinding +import javax.inject.Inject + +class LicenseAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (Library) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemLicenseBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + licenseItemName.text = item.name + licenseItemSummary.text = item.licenses.firstOrNull()?.name?.takeIf { it.isNotBlank() } + ?: item.artifactVersion + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemLicenseBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt index 2681680b..80e21c91 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseFragment.kt @@ -1,72 +1,67 @@ package io.github.wulkanowy.ui.modules.about.license import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.core.text.parseAsHtml +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library -import dagger.Lazy -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import com.mikepenz.aboutlibraries.util.withContext +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentLicenseBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_license.* import javax.inject.Inject -class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { +@AndroidEntryPoint +class LicenseFragment : BaseFragment(R.layout.fragment_license), + LicenseView, MainView.TitledView { @Inject lateinit var presenter: LicensePresenter @Inject - lateinit var licenseAdapter: FlexibleAdapter> - - @Inject - lateinit var libs: Lazy + lateinit var licenseAdapter: LicenseAdapter override val titleStringId get() = R.string.license_title - override val appLibraries: ArrayList? - get() = context?.let { - libs.get().prepareLibraries(it, emptyArray(), emptyArray(), autoDetect = true, checkCachedDetection = true, sort = true) - } + override val appLibraries by lazy { + Libs.Builder().withContext(requireContext()).build().libraries + } companion object { fun newInstance() = LicenseFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_license, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLicenseBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - with(licenseRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + licenseAdapter.onClickListener = presenter::onItemSelected + + with(binding.licenseRecycler) { + layoutManager = LinearLayoutManager(context) adapter = licenseAdapter } - licenseAdapter.setOnItemClickListener(presenter::onItemSelected) } - override fun updateData(data: List) { - licenseAdapter.updateDataSet(data) + override fun updateData(data: List) { + with(licenseAdapter) { + items = data + notifyDataSetChanged() + } } override fun openLicense(licenseHtml: String) { context?.let { - AlertDialog.Builder(it).apply { + MaterialAlertDialogBuilder(it).apply { setTitle(R.string.license_dialog_title) setMessage(licenseHtml.parseAsHtml()) setPositiveButton(android.R.string.ok) { _, _ -> } @@ -76,7 +71,7 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView { } override fun showProgress(show: Boolean) { - licenseProgress.visibility = if (show) VISIBLE else GONE + binding.licenseProgress.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt deleted file mode 100644 index 8dcb8922..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseItem.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.license - -import android.view.View -import com.mikepenz.aboutlibraries.entity.Library -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_license.* - -class LicenseItem(val library: Library) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_license - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - with(holder) { - licenseItemName.text = library.libraryName - licenseItemSummary.text = library.license?.licenseName?.takeIf { it.isNotBlank() } ?: library.libraryVersion - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as LicenseItem - - if (library != other.library) return false - - return true - } - - override fun hashCode() = library.hashCode() - - class ViewHolder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseModule.kt deleted file mode 100644 index 0ffc3d90..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseModule.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.license - -import android.content.Context -import com.mikepenz.aboutlibraries.Libs -import dagger.Module -import dagger.Provides -import io.github.wulkanowy.di.scopes.PerFragment - -@Module -class LicenseModule { - - @PerFragment - @Provides - fun provideLibs(context: Context) = Libs(context) -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt index dc48b098..ddcd5918 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt @@ -1,18 +1,19 @@ package io.github.wulkanowy.ui.modules.about.license -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.student.StudentRepository +import com.mikepenz.aboutlibraries.entity.Library +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Single +import io.github.wulkanowy.utils.DispatchersProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject class LicensePresenter @Inject constructor( - schedulers: SchedulersProvider, + private val dispatchers: DispatchersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: LicenseView) { super.onAttachView(view) @@ -20,17 +21,21 @@ class LicensePresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item !is LicenseItem) return - view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } } + fun onItemSelected(library: Library) { + view?.run { library.licenses.firstOrNull()?.licenseContent?.let { openLicense(it) } } } private fun loadData() { - disposable.add(Single.fromCallable { view?.appLibraries } - .map { it.map { library -> LicenseItem(library) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnEvent { _, _ -> view?.showProgress(false) } - .subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) })) + presenterScope.launch { + runCatching { + withContext(dispatchers.io) { + view?.appLibraries.orEmpty() + } + } + .onFailure { errorHandler.dispatch(it) } + .onSuccess { view?.updateData(it) } + + view?.showProgress(false) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt index 3939d3e8..6c97d875 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseView.kt @@ -5,11 +5,11 @@ import io.github.wulkanowy.ui.base.BaseView interface LicenseView : BaseView { - val appLibraries: ArrayList? + val appLibraries: List fun initView() - fun updateData(data: List) + fun updateData(data: List) fun openLicense(licenseHtml: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt deleted file mode 100644 index 4485cb3e..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.wulkanowy.ui.modules.about.logviewer - -import io.github.wulkanowy.data.repositories.logger.LoggerRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider -import timber.log.Timber -import javax.inject.Inject - -class LogViewerPresenter @Inject constructor( - schedulers: SchedulersProvider, - errorHandler: ErrorHandler, - studentRepository: StudentRepository, - private val loggerRepository: LoggerRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { - - override fun onAttachView(view: LogViewerView) { - super.onAttachView(view) - view.initView() - loadLogFile() - } - - fun onShareLogsSelected(): Boolean { - disposable.add(loggerRepository.getLogFiles() - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading logs files result: ${it.joinToString { it.name }}") - view?.shareLogs(it) - }, { - Timber.i("Loading logs files result: An exception occurred") - errorHandler.dispatch(it) - })) - return true - } - - fun onRefreshClick() { - loadLogFile() - } - - private fun loadLogFile() { - disposable.add(loggerRepository.getLastLogLines() - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading last log file result: load ${it.size} lines") - view?.setLines(it) - }, { - Timber.i("Loading last log file result: An exception occurred") - errorHandler.dispatch(it) - })) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt new file mode 100644 index 00000000..dbcb499e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt @@ -0,0 +1,3 @@ +package io.github.wulkanowy.ui.modules.account + +data class Account(val email: String, val isParent: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt new file mode 100644 index 00000000..523fbbd7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt @@ -0,0 +1,115 @@ +package io.github.wulkanowy.ui.modules.account + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.HeaderAccountBinding +import io.github.wulkanowy.databinding.ItemAccountBinding +import io.github.wulkanowy.utils.createNameInitialsDrawable +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nickOrName +import javax.inject.Inject + +class AccountAdapter @Inject constructor() : RecyclerView.Adapter() { + + var isAccountQuickDialogMode = false + + var items = emptyList>() + + var onClickListener: (StudentWithSemesters) -> Unit = {} + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + AccountItem.ViewType.HEADER.id -> HeaderViewHolder( + HeaderAccountBinding.inflate(inflater, parent, false) + ) + AccountItem.ViewType.ITEM.id -> ItemViewHolder( + ItemAccountBinding.inflate(inflater, parent, false) + ) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder( + holder.binding, + items[position].value as Account, + position + ) + is ItemViewHolder -> bindItemViewHolder( + holder.binding, + items[position].value as StudentWithSemesters + ) + } + } + + private fun bindHeaderViewHolder( + binding: HeaderAccountBinding, + account: Account, + position: Int + ) { + with(binding) { + accountHeaderDivider.visibility = if (position == 0) GONE else VISIBLE + accountHeaderEmail.text = account.email + accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student) + } + } + + @SuppressLint("SetTextI18n") + private fun bindItemViewHolder( + binding: ItemAccountBinding, + studentWithSemesters: StudentWithSemesters + ) { + val context = binding.root.context + val (student, semesters) = studentWithSemesters + val semester = semesters.maxByOrNull { it.semesterId } + val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor) + val checkBackgroundColor = + context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface) + val isDuplicatedStudent = items.filter { + if (it.value !is StudentWithSemesters) return@filter false + val studentToCompare = it.value.student + + studentToCompare.studentId == student.studentId + && studentToCompare.schoolSymbol == student.schoolSymbol + && studentToCompare.symbol == student.symbol + }.size > 1 && isAccountQuickDialogMode + + with(binding) { + accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}" + accountItemSchool.text = studentWithSemesters.student.schoolName + accountItemImage.setImageDrawable(avatar) + + with(accountItemAccountType) { + setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student) + isVisible = isDuplicatedStudent + } + + with(accountItemCheck) { + isVisible = student.isCurrent + borderColor = checkBackgroundColor + circleColor = checkBackgroundColor + } + + root.setOnClickListener { onClickListener(studentWithSemesters) } + } + } + + class HeaderViewHolder(val binding: HeaderAccountBinding) : + RecyclerView.ViewHolder(binding.root) + + class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt deleted file mode 100644 index cfff31c9..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt +++ /dev/null @@ -1,98 +0,0 @@ -package io.github.wulkanowy.ui.modules.account - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import android.widget.Toast.LENGTH_LONG -import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.R -import io.github.wulkanowy.ui.base.BaseDialogFragment -import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.dialog_account.* -import javax.inject.Inject - -class AccountDialog : BaseDialogFragment(), AccountView { - - @Inject - lateinit var presenter: AccountPresenter - - @Inject - lateinit var accountAdapter: FlexibleAdapter> - - companion object { - fun newInstance() = AccountDialog() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_account, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this) - } - - override fun initView() { - accountAdapter.setOnItemClickListener { presenter.onItemSelected(it) } - - accountDialogAdd.setOnClickListener { presenter.onAddSelected() } - accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() } - accountDialogRecycler.apply { - layoutManager = SmoothScrollLinearLayoutManager(context) - adapter = accountAdapter - } - } - - override fun updateData(data: List) { - accountAdapter.updateDataSet(data) - } - - override fun showError(text: String, error: Throwable) { - showMessage(text) - } - - override fun showMessage(text: String) { - Toast.makeText(context, text, LENGTH_LONG).show() - } - - override fun dismissView() { - dismiss() - } - - override fun openLoginView() { - activity?.let { - startActivity(LoginActivity.getStartIntent(it)) - } - } - - override fun showConfirmDialog() { - context?.let { - AlertDialog.Builder(it) - .setTitle(R.string.account_logout_student) - .setMessage(R.string.account_confirm) - .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() - } - } - - override fun recreateMainView() { - activity?.recreate() - } - - override fun onDestroy() { - presenter.onDetachView() - super.onDestroy() - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt new file mode 100644 index 00000000..f115372a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt @@ -0,0 +1,82 @@ +package io.github.wulkanowy.ui.modules.account + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import androidx.core.view.get +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.FragmentAccountBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment +import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import javax.inject.Inject + +@AndroidEntryPoint +class AccountFragment : BaseFragment(R.layout.fragment_account), + AccountView, MainView.TitledView { + + @Inject + lateinit var presenter: AccountPresenter + + @Inject + lateinit var accountAdapter: AccountAdapter + + companion object { + + fun newInstance() = AccountFragment() + } + + override val titleStringId = R.string.account_title + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + @Suppress("UNCHECKED_CAST") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding = FragmentAccountBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + binding.accountRecycler.apply { + layoutManager = LinearLayoutManager(context) + adapter = accountAdapter + } + + accountAdapter.onClickListener = presenter::onItemSelected + + binding.accountAdd.setOnClickListener { presenter.onAddSelected() } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + menu[0].isVisible = false + } + + override fun updateData(data: List>) { + with(accountAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun openLoginView() { + activity?.let { + startActivity(LoginActivity.getStartIntent(it)) + } + } + + override fun openAccountDetailsView(student: Student) { + (activity as? MainActivity)?.pushView(AccountDetailsFragment.newInstance(student)) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt index d3a3ee6a..05a4a69c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt @@ -1,59 +1,9 @@ package io.github.wulkanowy.ui.modules.account -import android.annotation.SuppressLint -import android.graphics.PorterDuff -import android.view.View -import androidx.core.graphics.ColorUtils -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_account.* +data class AccountItem(val value: T, val viewType: ViewType) { -class AccountItem(val student: Student) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - val context = holder.contentView.context - - val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AccountItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index e9b4b81e..77c1ffe6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,26 +1,25 @@ package io.github.wulkanowy.ui.modules.account -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.services.sync.SyncManager +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Single import timber.log.Timber import javax.inject.Inject class AccountPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val syncManager: SyncManager -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: AccountView) { super.onAttachView(view) view.initView() - Timber.i("Account dialog view was initialized") + Timber.i("Account view was initialized") loadData() } @@ -29,75 +28,29 @@ class AccountPresenter @Inject constructor( view?.openLoginView() } - fun onRemoveSelected() { - Timber.i("Select remove account") - view?.showConfirmDialog() - } - - fun onLogoutConfirm() { - Timber.i("Attempt to logout current user ") - disposable.add(studentRepository.getCurrentStudent() - .flatMapCompletable { studentRepository.logoutStudent(it) } - .andThen(studentRepository.getSavedStudents(false)) - .flatMap { - if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it } - else Single.just(it) - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.dismissView() } - .subscribe({ - view?.apply { - if (it.isEmpty()) { - Timber.i("Logout result: Open login view") - syncManager.stopSyncWorker() - openClearLoginView() - } else { - Timber.i("Logout result: Switch to another student") - recreateMainView() - } - } - }, { - Timber.i("Logout result: An exception occurred") - errorHandler.dispatch(it) - })) - } - - fun onItemSelected(item: AbstractFlexibleItem<*>) { - if (item is AccountItem) { - Timber.i("Select student item ${item.student.id}") - if (item.student.isCurrent) { - view?.dismissView() - } else { - Timber.i("Attempt to change a student") - disposable.add(studentRepository.switchStudent(item.student) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.dismissView() } - .subscribe({ - Timber.i("Change a student result: Success") - view?.recreateMainView() - }, { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it) - })) - } - } + fun onItemSelected(studentWithSemesters: StudentWithSemesters) { + view?.openAccountDetailsView(studentWithSemesters.student) } private fun loadData() { - Timber.i("Loading account data started") - disposable.add(studentRepository.getSavedStudents(false) - .map { it.map { item -> AccountItem(item) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading account result: Success") - view?.updateData(it) - }, { - Timber.i("Loading account result: An exception occurred") - errorHandler.dispatch(it) - })) + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load account data") + .onResourceSuccess { view?.updateData(createAccountItems(it)) } + .onResourceError(errorHandler::dispatch) + .launch("load") + } + + private fun createAccountItems(items: List): List> { + return items.groupBy { + Account("${it.student.userName} (${it.student.email})", it.student.isParent) + } + .map { (account, students) -> + listOf( + AccountItem(account, AccountItem.ViewType.HEADER) + ) + students.map { student -> + AccountItem(student, AccountItem.ViewType.ITEM) + } + } + .flatten() } } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt index ede5023b..56fcb0a3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt @@ -1,19 +1,15 @@ package io.github.wulkanowy.ui.modules.account +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface AccountView : BaseView { fun initView() - fun updateData(data: List) - - fun dismissView() - - fun showConfirmDialog() + fun updateData(data: List>) fun openLoginView() - fun recreateMainView() + fun openAccountDetailsView(student: Student) } - diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt new file mode 100644 index 00000000..d6bc6154 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -0,0 +1,171 @@ +package io.github.wulkanowy.ui.modules.account.accountdetails + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf +import androidx.core.view.get +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView +import io.github.wulkanowy.utils.createNameInitialsDrawable +import io.github.wulkanowy.utils.nickOrName +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class AccountDetailsFragment : + BaseFragment(R.layout.fragment_account_details), + AccountDetailsView, MainView.TitledView { + + @Inject + lateinit var presenter: AccountDetailsPresenter + + override val titleStringId = R.string.account_details_title + + companion object { + + private const val ARGUMENT_KEY = "Data" + + fun newInstance(student: Student) = AccountDetailsFragment().apply { + arguments = bundleOf(ARGUMENT_KEY to student) + } + } + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAccountDetailsBinding.bind(view) + presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) + } + + override fun initView() { + binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() } + binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() } + binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() } + + binding.accountDetailsPersonalData.setOnClickListener { + presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL) + } + binding.accountDetailsAddressData.setOnClickListener { + presenter.onStudentInfoSelected(StudentInfoView.Type.ADDRESS) + } + binding.accountDetailsContactData.setOnClickListener { + presenter.onStudentInfoSelected(StudentInfoView.Type.CONTACT) + } + binding.accountDetailsFamilyData.setOnClickListener { + presenter.onStudentInfoSelected(StudentInfoView.Type.FAMILY) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + menu[0].isVisible = false + inflater.inflate(R.menu.action_menu_account_details, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.accountDetailsMenuEdit) { + presenter.onAccountEditSelected() + true + } else false + } + + override fun showAccountData(student: Student) { + with(binding) { + accountDetailsCheck.isVisible = student.isCurrent + accountDetailsName.text = student.nickOrName + accountDetailsSchool.text = student.schoolName + accountDetailsAvatar.setImageDrawable( + requireContext().createNameInitialsDrawable( + student.nickOrName, + student.avatarColor + ) + ) + } + } + + override fun enableSelectStudentButton(enable: Boolean) { + binding.accountDetailsSelect.isEnabled = enable + } + + override fun showAccountEditDetailsDialog(student: Student) { + (requireActivity() as MainActivity).showDialogFragment( + AccountEditDialog.newInstance(student) + ) + } + + override fun showLogoutConfirmDialog() { + context?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.account_logout_student) + .setMessage(R.string.account_confirm) + .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + } + + override fun popViewToMain() { + (requireActivity() as MainActivity).popView(2) + } + + override fun popViewToAccounts() { + (requireActivity() as MainActivity).popView(1) + } + + override fun recreateMainView() { + requireActivity().recreate() + } + + override fun openStudentInfoView( + infoType: StudentInfoView.Type, + studentWithSemesters: StudentWithSemesters + ) { + (requireActivity() as MainActivity).pushView( + StudentInfoFragment.newInstance( + infoType, + studentWithSemesters + ) + ) + } + + override fun showErrorView(show: Boolean) { + binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.accountDetailsErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showContent(show: Boolean) { + binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt new file mode 100644 index 00000000..5d68ff2e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt @@ -0,0 +1,156 @@ +package io.github.wulkanowy.ui.modules.account.accountdetails + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.SyncManager +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView +import timber.log.Timber +import javax.inject.Inject + +class AccountDetailsPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val syncManager: SyncManager +) : BasePresenter(errorHandler, studentRepository) { + + private var studentWithSemesters: StudentWithSemesters? = null + + private lateinit var lastError: Throwable + + private var studentId: Long? = null + + fun onAttachView(view: AccountDetailsView, student: Student) { + super.onAttachView(view) + studentId = student.id + + view.initView() + errorHandler.showErrorMessage = ::showErrorViewOnError + Timber.i("Account details view was initialized") + loadData() + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun loadData() { + resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) } + .logResourceStatus("loading account details view") + .onResourceLoading { + view?.run { + showProgress(true) + showContent(false) + } + } + .onResourceSuccess { + studentWithSemesters = it + view?.run { + showAccountData(studentWithSemesters!!.student) + enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent) + showContent(true) + showErrorView(false) + } + } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError(errorHandler::dispatch) + .launch() + } + + fun onAccountEditSelected() { + studentWithSemesters?.let { + view?.showAccountEditDetailsDialog(it.student) + } + } + + fun onStudentInfoSelected(infoType: StudentInfoView.Type) { + studentWithSemesters?.let { + view?.openStudentInfoView(infoType, it) + } + } + + fun onStudentSelect() { + if (studentWithSemesters == null) return + + Timber.i("Select student ${studentWithSemesters!!.student.id}") + + resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popViewToMain() } + .onResourceError(errorHandler::dispatch) + .launch("switch") + } + + fun onRemoveSelected() { + Timber.i("Select remove account") + view?.showLogoutConfirmDialog() + } + + fun onLogoutConfirm() { + if (studentWithSemesters == null) return + + resourceFlow { + val studentToLogout = studentWithSemesters!!.student + + studentRepository.logoutStudent(studentToLogout) + val students = studentRepository.getSavedStudents(false) + + if (studentToLogout.isCurrent && students.isNotEmpty()) { + studentRepository.switchStudent(students[0]) + } + + students + } + .logResourceStatus("logout user") + .onResourceSuccess { + view?.run { + when { + it.isEmpty() -> { + Timber.i("Logout result: Open login view") + syncManager.stopSyncWorker() + openClearLoginView() + } + studentWithSemesters?.student?.isCurrent == true -> { + Timber.i("Logout result: Logout student and switch to another") + recreateMainView() + } + else -> { + Timber.i("Logout result: Logout student") + recreateMainView() + } + } + } + } + .onResourceNotLoading { + if (studentWithSemesters?.student?.isCurrent == true) { + view?.popViewToMain() + } else { + view?.popViewToAccounts() + } + } + .onResourceError(errorHandler::dispatch) + .launch("logout") + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + lastError = error + setErrorDetails(message) + showErrorView(true) + showContent(false) + showProgress(false) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt new file mode 100644 index 00000000..aeb743fa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.ui.modules.account.accountdetails + +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView + +interface AccountDetailsView : BaseView { + + fun initView() + + fun showAccountData(student: Student) + + fun showAccountEditDetailsDialog(student: Student) + + fun showLogoutConfirmDialog() + + fun popViewToMain() + + fun popViewToAccounts() + + fun recreateMainView() + + fun enableSelectStudentButton(enable: Boolean) + + fun openStudentInfoView( + infoType: StudentInfoView.Type, + studentWithSemesters: StudentWithSemesters + ) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun showContent(show: Boolean) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt new file mode 100644 index 00000000..66e39fc7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt @@ -0,0 +1,70 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemAccountEditColorBinding +import javax.inject.Inject + +class AccountEditColorAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = listOf() + + var selectedColor = 0 + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemAccountEditColorBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("RestrictedApi", "NewApi") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + accountEditItemColor.setImageDrawable(GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(item) + }) + + accountEditItemColorContainer.foreground = item.createForegroundDrawable() + accountEditCheck.isVisible = selectedColor == item + + root.setOnClickListener { + val oldSelectedPosition = items.indexOf(selectedColor) + selectedColor = item + + notifyItemChanged(oldSelectedPosition) + notifyItemChanged(position) + } + } + } + + private fun Int.createForegroundDrawable(): Drawable { + val mask = GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(Color.BLACK) + } + return RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask) + } + + private inline val Int.rippleColor: Int + get() { + val hsv = FloatArray(3) + Color.colorToHSV(this, hsv) + hsv[2] = hsv[2] * 0.5f + return Color.HSVToColor(hsv) + } + + class ViewHolder(val binding: ItemAccountEditColorBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt new file mode 100644 index 00000000..4229579c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt @@ -0,0 +1,93 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.DialogAccountEditBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class AccountEditDialog : BaseDialogFragment(), AccountEditView { + + @Inject + lateinit var presenter: AccountEditPresenter + + @Inject + lateinit var accountEditColorAdapter: AccountEditColorAdapter + + companion object { + + private const val ARGUMENT_KEY = "student_with_semesters" + + fun newInstance(student: Student) = AccountEditDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to student) + } + } + + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAccountEditBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) + } + + override fun initView() { + with(binding) { + accountEditDetailsCancel.setOnClickListener { dismiss() } + accountEditDetailsSave.setOnClickListener { + presenter.changeStudentNickAndAvatar( + binding.accountEditDetailsNickText.text.toString(), + accountEditColorAdapter.selectedColor + ) + } + + with(binding.accountEditColors) { + layoutManager = GridLayoutManager(context, 4) + adapter = accountEditColorAdapter + } + } + } + + override fun updateSelectedColorData(color: Int) { + with(accountEditColorAdapter) { + selectedColor = color + notifyDataSetChanged() + } + } + + override fun updateColorsData(colors: List) { + with(accountEditColorAdapter) { + items = colors + notifyDataSetChanged() + } + } + + override fun showCurrentNick(nick: String) { + binding.accountEditDetailsNickText.setText(nick) + } + + override fun popView() { + dismiss() + } + + override fun recreateMainView() { + activity?.recreate() + } + + override fun onDestroyView() { + super.onDestroyView() + presenter.onDetachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt new file mode 100644 index 00000000..c401158e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt @@ -0,0 +1,60 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AppInfo +import timber.log.Timber +import javax.inject.Inject + +class AccountEditPresenter @Inject constructor( + appInfo: AppInfo, + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + lateinit var student: Student + + private val colors = appInfo.defaultColorsForAvatar.map { it.toInt() } + + fun onAttachView(view: AccountEditView, student: Student) { + super.onAttachView(view) + this.student = student + + with(view) { + initView() + showCurrentNick(student.nick.trim()) + } + Timber.i("Account edit dialog view was initialized") + loadData() + + view.updateColorsData(colors) + } + + private fun loadData() { + resourceFlow { studentRepository.getStudentById(student.id, false).avatarColor } + .logResourceStatus("load student") + .onResourceSuccess { view?.updateSelectedColorData(it.toInt()) } + .onResourceError(errorHandler::dispatch) + .launch("load_data") + } + + fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) { + resourceFlow { + val studentNick = StudentNickAndAvatar( + nick = nick.trim(), + avatarColor = avatarColor.toLong() + ).apply { id = student.id } + + studentRepository.updateStudentNickAndAvatar(studentNick) + } + .logResourceStatus("change student nick and avatar") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) + .launch("update_student") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt new file mode 100644 index 00000000..517492de --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import io.github.wulkanowy.ui.base.BaseView + +interface AccountEditView : BaseView { + + fun initView() + + fun popView() + + fun recreateMainView() + + fun showCurrentNick(nick: String) + + fun updateSelectedColorData(color: Int) + + fun updateColorsData(colors: List) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt new file mode 100644 index 00000000..2d2dccec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt @@ -0,0 +1,93 @@ +package io.github.wulkanowy.ui.modules.account.accountquick + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.DialogAccountQuickBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.ui.modules.account.AccountAdapter +import io.github.wulkanowy.ui.modules.account.AccountFragment +import io.github.wulkanowy.ui.modules.account.AccountItem +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class AccountQuickDialog : BaseDialogFragment(), AccountQuickView { + + @Inject + lateinit var accountAdapter: AccountAdapter + + @Inject + lateinit var presenter: AccountQuickPresenter + + companion object { + + private const val STUDENTS_ARGUMENT_KEY = "students" + + fun newInstance(studentsWithSemesters: List) = + AccountQuickDialog().apply { + arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray()) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAccountQuickBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val studentsWithSemesters = requireArguments() + .serializable>(STUDENTS_ARGUMENT_KEY).toList() + + presenter.onAttachView(this, studentsWithSemesters) + } + + override fun initView() { + binding.accountQuickDialogManger.setOnClickListener { presenter.onManagerSelected() } + + with(accountAdapter) { + isAccountQuickDialogMode = true + onClickListener = presenter::onStudentSelect + } + + with(binding.accountQuickDialogRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = accountAdapter + } + } + + override fun updateData(data: List>) { + with(accountAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun popView() { + dismiss() + } + + override fun recreateMainView() { + activity?.recreate() + } + + override fun openAccountView() { + (requireActivity() as MainActivity).pushView(AccountFragment.newInstance()) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt new file mode 100644 index 00000000..32c07f80 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.ui.modules.account.accountquick + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.ui.modules.account.AccountItem +import timber.log.Timber +import javax.inject.Inject + +class AccountQuickPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var studentsWithSemesters: List + + fun onAttachView(view: AccountQuickView, studentsWithSemesters: List) { + super.onAttachView(view) + this.studentsWithSemesters = studentsWithSemesters + + view.initView() + Timber.i("Account quick dialog view was initialized") + view.updateData(createAccountItems(studentsWithSemesters)) + } + + fun onManagerSelected() { + view?.run { + openAccountView() + popView() + } + } + + fun onStudentSelect(studentWithSemesters: StudentWithSemesters) { + Timber.i("Select student ${studentWithSemesters.student.id}") + + if (studentWithSemesters.student.isCurrent) { + view?.popView() + return + } + + resourceFlow { studentRepository.switchStudent(studentWithSemesters) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) + .launch("switch") + } + + private fun createAccountItems(items: List) = items.map { + AccountItem(it, AccountItem.ViewType.ITEM) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt new file mode 100644 index 00000000..4a9420d9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.ui.modules.account.accountquick + +import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.account.AccountItem + +interface AccountQuickView : BaseView { + + fun initView() + + fun updateData(data: List>) + + fun recreateMainView() + + fun popView() + + fun openAccountView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 75f99840..f5689ec8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -1,12 +1,117 @@ package io.github.wulkanowy.ui.modules.attendance -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.IFlexible +import android.content.res.ColorStateList +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.enums.SentExcuseStatus +import io.github.wulkanowy.databinding.ItemAttendanceBinding +import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.isExcusableOrNotExcused +import javax.inject.Inject -class AttendanceAdapter> : FlexibleAdapter(null, null, true) { +class AttendanceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() var excuseActionMode: Boolean = false + var onClickListener: (Attendance) -> Unit = {} + var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemAttendanceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val context = holder.binding.root.context + val item = items[position] + + with(holder.binding) { + attendanceItemNumber.text = item.number.toString() + attendanceItemSubject.text = item.subject + .ifBlank { context.getString(R.string.all_no_data) } + attendanceItemDescription.setText(item.descriptionRes) + + attendanceItemDescription.setTextColor( + context.getThemeAttrColor( + when { + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.textColorSecondary + } + ) + ) + + if (item.exemption || item.excused) { + attendanceItemDescription.setTypeface(null, Typeface.BOLD) + } else { + attendanceItemDescription.setTypeface(null, Typeface.NORMAL) + } + + attendanceItemAlert.isVisible = + item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) } + + attendanceItemAlert.imageTintList = ColorStateList.valueOf( + context.getThemeAttrColor( + when { + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.colorPrimary + } + ) + ) + attendanceItemNumber.visibility = View.GONE + attendanceItemExcuseInfo.visibility = View.GONE + attendanceItemExcuseCheckbox.visibility = View.GONE + attendanceItemExcuseCheckbox.isChecked = false + attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked -> + onExcuseCheckboxSelect(item, checked) + } + + when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it) }) { + SentExcuseStatus.WAITING -> { + attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting) + attendanceItemExcuseInfo.visibility = View.VISIBLE + attendanceItemAlert.visibility = View.INVISIBLE + } + + SentExcuseStatus.DENIED -> { + attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) + attendanceItemExcuseInfo.visibility = View.VISIBLE + } + + else -> { + if (item.isExcusableOrNotExcused && excuseActionMode) { + attendanceItemNumber.visibility = View.GONE + attendanceItemExcuseCheckbox.visibility = View.VISIBLE + } else { + attendanceItemNumber.visibility = View.VISIBLE + attendanceItemExcuseCheckbox.visibility = View.GONE + } + } + } + root.setOnClickListener { + onClickListener(item) + + with(attendanceItemExcuseCheckbox) { + if (excuseActionMode && isVisible) { + isChecked = !isChecked + } + } + } + } + } + + class ItemViewHolder(val binding: ItemAttendanceBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index 611dd999..63503313 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -1,48 +1,64 @@ package io.github.wulkanowy.ui.modules.attendance +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.databinding.DialogAttendanceBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_attendance.* -class AttendanceDialog : DialogFragment() { +@AndroidEntryPoint +class AttendanceDialog : BaseDialogFragment() { private lateinit var attendance: Attendance companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Attendance): AttendanceDialog { - return AttendanceDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } - } + fun newInstance(exam: Attendance) = AttendanceDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to exam) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - attendance = getSerializable(ARGUMENT_KEY) as Attendance + attendance = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAttendanceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + attendanceDialogSubjectValue.text = attendance.subject + attendanceDialogDescriptionValue.setText(attendance.descriptionRes) + attendanceDialogDescriptionValue.setTextColor( + root.context.getThemeAttrColor( + when { + attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence + attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.textColorSecondary + } + ) + ) + + attendanceDialogDateValue.text = attendance.date.toFormattedString() + attendanceDialogNumberValue.text = attendance.number.toString() + attendanceDialogClose.setOnClickListener { dismiss() } } } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_attendance, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - attendanceDialogSubject.text = attendance.subject - attendanceDialogDescription.text = attendance.name - attendanceDialogDate.text = attendance.date.toFormattedString() - attendanceDialogNumber.text = attendance.number.toString() - attendanceDialogClose.setOnClickListener { dismiss() } - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 9969b1c7..07649e43 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -2,43 +2,41 @@ package io.github.wulkanowy.ui.modules.attendance import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog +import android.view.* +import android.view.View.* import androidx.appcompat.view.ActionMode -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.databinding.DialogExcuseBinding +import io.github.wulkanowy.databinding.FragmentAttendanceBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.attendance.calculator.AttendanceCalculatorFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.SchooldaysRangeLimiter +import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.dialog_excuse.* -import kotlinx.android.synthetic.main.fragment_attendance.* -import org.threeten.bp.LocalDate +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.openMaterialDatePicker +import java.time.LocalDate import javax.inject.Inject -class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView, +@AndroidEntryPoint +class AttendanceFragment : BaseFragment(R.layout.fragment_attendance), + AttendanceView, MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: AttendancePresenter @Inject - lateinit var attendanceAdapter: AttendanceAdapter> + lateinit var attendanceAdapter: AttendanceAdapter override val excuseSuccessString: String get() = getString(R.string.attendance_excuse_success) @@ -54,17 +52,18 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie override val titleStringId get() = R.string.attendance_title - override val isViewEmpty get() = attendanceAdapter.isEmpty + override val isViewEmpty get() = attendanceAdapter.items.isEmpty() override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode private var actionMode: ActionMode? = null + private val actionModeCallback = object : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { val inflater = mode.menuInflater - inflater.inflate(R.menu.context_menu_excuse, menu) + inflater.inflate(R.menu.context_menu_attendance, menu) return true } @@ -86,44 +85,48 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_attendance, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = attendanceRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceBinding.bind(view) + messageContainer = binding.attendanceRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { - attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected) - attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect - - with(attendanceRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) - adapter = attendanceAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + with(attendanceAdapter) { + onClickListener = presenter::onAttendanceItemSelected + onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect } - attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - attendanceErrorRetry.setOnClickListener { presenter.onRetry() } - attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding.attendanceRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = attendanceAdapter + addItemDecoration(DividerItemDecoration(context)) + } - attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } - attendanceNavDate.setOnClickListener { presenter.onPickDate() } - attendanceNextButton.setOnClickListener { presenter.onNextDay() } + with(binding) { + attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + attendanceSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + attendanceErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() } - attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } + attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } + attendanceNavDate.setOnClickListener { presenter.onPickDate() } + attendanceNextButton.setOnClickListener { presenter.onNextDay() } - attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } + + attendanceNavContainer.elevation = requireContext().dpToPx(3f) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -132,23 +135,34 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected() + else if (item.itemId == R.id.attendanceMenuCalculator) presenter.onCalculatorSwitchSelected() else false } - override fun updateData(data: List) { - attendanceAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(attendanceAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateNavigationDay(date: String) { - attendanceNavDate.text = date + binding.attendanceNavDate.text = date + } + + override fun showNavigation(show: Boolean) { + binding.attendanceNavContainer.isVisible = show } override fun clearData() { - attendanceAdapter.clear() + with(attendanceAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun resetView() { - attendanceRecycler.smoothScrollToPosition(0) + binding.attendanceRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { @@ -164,73 +178,75 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun showEmpty(show: Boolean) { - attendanceEmpty.visibility = if (show) VISIBLE else GONE + binding.attendanceEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - attendanceError.visibility = if (show) VISIBLE else GONE + binding.attendanceError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - attendanceErrorMessage.text = message + binding.attendanceErrorMessage.text = message } override fun showProgress(show: Boolean) { - attendanceProgress.visibility = if (show) VISIBLE else GONE + binding.attendanceProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - attendanceSwipe.isEnabled = enable + binding.attendanceSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - attendanceRecycler.visibility = if (show) VISIBLE else GONE + binding.attendanceRecycler.visibility = if (show) VISIBLE else GONE } - override fun hideRefresh() { - attendanceSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.attendanceSwipe.isRefreshing = show } override fun showPreButton(show: Boolean) { - attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE + binding.attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showNextButton(show: Boolean) { - attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding.attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showExcuseButton(show: Boolean) { - attendanceExcuseButton.visibility = if (show) VISIBLE else GONE + binding.attendanceExcuseButton.isVisible = show } override fun showAttendanceDialog(lesson: Attendance) { (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson)) } - override fun showDatePickerDialog(currentDate: LocalDate) { - val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth -> - presenter.onDateSet(year, month + 1, dayOfMonth) - } - val datePickerDialog = DatePickerDialog.newInstance(dateSetListener, - currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth) - - with(datePickerDialog) { - setDateRangeLimiter(SchooldaysRangeLimiter()) - version = DatePickerDialog.Version.VERSION_2 - scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL - show(this@AttendanceFragment.parentFragmentManager, null) - } + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().plusWeeks(1), + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun showExcuseDialog() { - AlertDialog.Builder(requireContext()) + val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context)) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.attendance_excuse_title) - .setView(R.layout.dialog_excuse) + .setView(dialogBinding.root) .setNegativeButton(android.R.string.cancel) { _, _ -> } .create() .apply { - setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ -> - presenter.onExcuseDialogSubmit(excuseReason.text?.toString().orEmpty()) + setButton( + BUTTON_POSITIVE, + getString(R.string.attendance_excuse_dialog_submit) + ) { _, _ -> + presenter.onExcuseDialogSubmit( + dialogBinding.excuseReason.text?.toString().orEmpty() + ) } }.show() } @@ -239,24 +255,45 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie (activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance()) } + override fun openCalculatorView() { + (activity as? MainActivity)?.pushView(AttendanceCalculatorFragment.newInstance()) + } + override fun startActionMode() { actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback) } + override fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) { + val reasonFullText = getString( + R.string.attendance_excuse_formula, + date, + numbers, + if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "", + reason.ifBlank { "" } + ) + startActivity(SendMessageActivity.getStartIntent(requireContext(), reasonFullText)) + } + override fun showExcuseCheckboxes(show: Boolean) { - attendanceAdapter.apply { + with(attendanceAdapter) { excuseActionMode = show notifyDataSetChanged() } } + override fun showDayNavigation(show: Boolean) { + binding.attendanceNavContainer.isVisible = show + } + override fun finishActionMode() { actionMode?.finish() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + presenter.currentDate?.let { + outState.putLong(SAVED_DATE_KEY, it.toEpochDay()) + } } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt deleted file mode 100644 index 7355aec2..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceItem.kt +++ /dev/null @@ -1,97 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance - -import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import androidx.core.view.isVisible -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_attendance.* - -class AttendanceItem(val attendance: Attendance) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_attendance - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - attendanceItemNumber.text = attendance.number.toString() - attendanceItemSubject.text = attendance.subject - attendanceItemDescription.text = attendance.name - attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE } - attendanceItemNumber.visibility = GONE - attendanceItemExcuseInfo.visibility = GONE - attendanceItemExcuseCheckbox.visibility = GONE - attendanceItemExcuseCheckbox.isChecked = false - attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked -> - (adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked) - } - - when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) { - SentExcuseStatus.WAITING -> { - attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting) - attendanceItemExcuseInfo.visibility = VISIBLE - attendanceItemAlert.visibility = INVISIBLE - } - SentExcuseStatus.DENIED -> { - attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) - attendanceItemExcuseInfo.visibility = VISIBLE - } - else -> { - if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) { - attendanceItemNumber.visibility = GONE - attendanceItemExcuseCheckbox.visibility = VISIBLE - } else { - attendanceItemNumber.visibility = VISIBLE - attendanceItemExcuseCheckbox.visibility = GONE - } - } - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceItem - - if (attendance != other.attendance) return false - - return true - } - - override fun hashCode(): Int { - var result = attendance.hashCode() - result = 31 * result + attendance.id.toInt() - return result - } - - class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - - override fun onClick(view: View?) { - super.onClick(view) - attendanceItemExcuseCheckbox.apply { - if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) { - isChecked = !isChecked - } - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt deleted file mode 100644 index eb35fea1..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance - -import dagger.Module -import dagger.Provides -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -@Module -class AttendanceModule { - - @Provides - fun provideAttendanceFlexibleAdapter() = AttendanceAdapter>() -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 7fc04474..586a41ad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,84 +1,109 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceLoading +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.AttendanceRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday +import io.github.wulkanowy.utils.isExcusableOrNotExcused import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.toFormattedString -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.ofEpochDay +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.LocalDate.ofEpochDay import javax.inject.Inject class AttendancePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val attendanceRepository: AttendanceRepository, private val semesterRepository: SemesterRepository, private val prefRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { - private var baseDate: LocalDate = now().previousOrSameSchoolDay + private var initialDate: LocalDate? = null + private var isWeekendHasLessons: Boolean = false - lateinit var currentDate: LocalDate + var currentDate: LocalDate? = null private set private lateinit var lastError: Throwable private val attendanceToExcuseList = mutableListOf() + private var isVulcanExcusedFunctionEnabled = false + fun onAttachView(view: AttendanceView, date: Long?) { super.onAttachView(view) view.initView() Timber.i("Attendance view was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(ofEpochDay(date ?: baseDate.toEpochDay())) - if (currentDate.isHolidays) setBaseDateOnHolidays() - reloadView() + currentDate = date?.let(::ofEpochDay) + loadData() } fun onPreviousDay() { + val date = if (isWeekendHasLessons) { + currentDate?.minusDays(1) + } else currentDate?.previousSchoolDay + view?.finishActionMode() attendanceToExcuseList.clear() - loadData(currentDate.previousSchoolDay) - reloadView() + reloadView(date ?: return) + loadData() } fun onNextDay() { + val date = if (isWeekendHasLessons) { + currentDate?.plusDays(1) + } else currentDate?.nextSchoolDay + view?.finishActionMode() attendanceToExcuseList.clear() - loadData(currentDate.nextSchoolDay) - reloadView() + reloadView(date ?: return) + loadData() } fun onPickDate() { - view?.showDatePickerDialog(currentDate) + view?.showDatePickerDialog(currentDate ?: return) } fun onDateSet(year: Int, month: Int, day: Int) { - loadData(LocalDate.of(year, month, day)) - reloadView() + reloadView(LocalDate.of(year, month, day)) + loadData() } fun onSwipeRefresh() { Timber.i("Force refreshing the attendance") - loadData(currentDate, true) + loadData(true) } fun onRetry() { @@ -86,7 +111,7 @@ class AttendancePresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(currentDate, true) + loadData(true) } fun onDetailsClick() { @@ -95,15 +120,17 @@ class AttendancePresenter @Inject constructor( fun onViewReselected() { Timber.i("Attendance view is reselected") - view?.also { view -> + view?.let { view -> if (view.currentStackSize == 1) { - baseDate.also { - if (currentDate != it) { - loadData(it) - reloadView() - } else if (!view.isViewEmpty) view.resetView() + if (currentDate != initialDate) { + reloadView(initialDate ?: return) + loadData() + } else if (!view.isViewEmpty) { + view.resetView() } - } else view.popView() + } else { + view.popView() + } } } @@ -111,11 +138,11 @@ class AttendancePresenter @Inject constructor( view?.finishActionMode() } - fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { + fun onAttendanceItemSelected(attendance: Attendance) { view?.apply { - if (item is AttendanceItem && !excuseActionMode) { - Timber.i("Select attendance item ${item.attendance.id}") - showAttendanceDialog(item.attendance) + if (!excuseActionMode) { + Timber.i("Select attendance item ${attendance.id}") + showAttendanceDialog(attendance) } } } @@ -144,13 +171,31 @@ class AttendancePresenter @Inject constructor( fun onExcuseDialogSubmit(reason: String) { view?.finishActionMode() - excuseAbsence(if (reason != "") reason else null, attendanceToExcuseList.toList()) + + if (attendanceToExcuseList.isEmpty()) return + + if (isVulcanExcusedFunctionEnabled) { + excuseAbsence( + reason = reason.takeIf { it.isNotBlank() }, + toExcuseList = attendanceToExcuseList.toList() + ) + } else { + val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number } + + view?.startSendMessageIntent( + date = attendanceToExcuseList[0].date, + numbers = attendanceToExcuseNumbers.joinToString(", "), + reason = reason + ) + } } fun onPrepareActionMode(): Boolean { view?.apply { showExcuseCheckboxes(true) showExcuseButton(false) + enableSwipe(false) + showDayNavigation(false) } attendanceToExcuseList.clear() return true @@ -160,6 +205,8 @@ class AttendancePresenter @Inject constructor( view?.apply { showExcuseCheckboxes(false) showExcuseButton(true) + enableSwipe(true) + showDayNavigation(true) } } @@ -168,99 +215,147 @@ class AttendancePresenter @Inject constructor( return true } - private fun setBaseDateOnHolidays() { - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + fun onCalculatorSwitchSelected(): Boolean { + view?.openCalculatorView() + return true } - private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading attendance data started") - currentDate = date - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - attendanceRepository.getAttendance(student, semester, date, date, forceRefresh) - } - } - .map { list -> - if (prefRepository.isShowPresent) list - else list.filter { !it.presence } - } - .delay(200, MILLISECONDS) - .map { items -> items.map { AttendanceItem(it) } } - .map { items -> items.sortedBy { it.attendance.number } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading attendance result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - showExcuseButton(it.any { item -> item.attendance.excusable }) - } - analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading attendance result: An exception occurred") - errorHandler.dispatch(it) - } + + var isParent = false + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + isParent = student.isParent + + val semester = semesterRepository.getCurrentSemester(student) + + checkInitialAndCurrentDate(semester) + attendanceRepository.getAttendance( + student = student, + semester = semester, + start = currentDate ?: now(), + end = currentDate ?: now(), + forceRefresh = forceRefresh ) } + .logResourceStatus("load attendance") + .onResourceLoading { + view?.showExcuseButton(false) + } + .mapResourceData { + if (prefRepository.isShowPresent) { + it + } else { + it.filter { item -> !item.presence } + }.sortedBy { item -> item.number } + } + .onResourceData { + isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it) + + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + updateData(it) + reloadNavigation() + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable } + val anyExcusables = it.any { it.isExcusableOrNotExcused } + view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled)) + + analytics.logEvent( + "load_data", + "type" to "attendance", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private suspend fun checkInitialAndCurrentDate(semester: Semester) { + if (initialDate == null) { + val lessons = attendanceRepository.getAttendanceFromDatabase( + semester = semester, + start = now().monday, + end = now().sunday, + ).firstOrNull().orEmpty() + isWeekendHasLessons = isWeekendHasLessons(lessons) + initialDate = getInitialDate(semester) + } + + if (currentDate == null) { + currentDate = initialDate + } + } + + private fun isWeekendHasLessons( + lessons: List, + ): Boolean = lessons.any { + it.date.dayOfWeek in listOf( + DayOfWeek.SATURDAY, + DayOfWeek.SUNDAY, + ) + } + + private fun getInitialDate(semester: Semester): LocalDate { + val now = now() + + return when { + now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear) + isWeekendHasLessons -> now + else -> now.previousOrSameSchoolDay + } } private fun excuseAbsence(reason: String?, toExcuseList: List) { - Timber.i("Excusing absence started") - disposable.apply { - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) - } + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { + Timber.i("Excusing absence started") + showProgress(true) + showContent(false) + showExcuseButton(false) } - .delay(200, MILLISECONDS) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - showProgress(true) - showContent(false) - showExcuseButton(false) - } - } - .subscribe({ + + is Resource.Success -> { Timber.i("Excusing for absence result: Success") analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) attendanceToExcuseList.clear() - view?.apply { + view?.run { showExcuseButton(false) showMessage(excuseSuccessString) + showContent(true) + showProgress(false) } - loadData(currentDate, true) - }) { + loadData(forceRefresh = true) + } + + is Resource.Error -> { Timber.i("Excusing for absence result: An exception occurred") - view?.showProgress(false) - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error) + loadData() + } + } + }.launch("excuse") } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -274,11 +369,14 @@ class AttendancePresenter @Inject constructor( } } - private fun reloadView() { - Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}") + private fun reloadView(date: LocalDate) { + currentDate = date + + Timber.i("Reload attendance view with the date ${currentDate?.toFormattedString()}") view?.apply { showProgress(true) enableSwipe(false) + showRefresh(false) showContent(false) showEmpty(false) showErrorView(false) @@ -289,10 +387,13 @@ class AttendancePresenter @Inject constructor( @SuppressLint("DefaultLocale") private fun reloadNavigation() { + val currentDate = currentDate ?: return + view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays) - updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize()) + updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) + showNavigation(true) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index 03e95053..f51ce7c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.attendance import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.ui.base.BaseView -import org.threeten.bp.LocalDate +import java.time.LocalDate interface AttendanceView : BaseView { @@ -18,13 +18,13 @@ interface AttendanceView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun updateNavigationDay(date: String) fun clearData() - fun hideRefresh() + fun showRefresh(show: Boolean) fun resetView() @@ -40,6 +40,8 @@ interface AttendanceView : BaseView { fun showContent(show: Boolean) + fun showNavigation(show: Boolean) + fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) @@ -48,16 +50,22 @@ interface AttendanceView : BaseView { fun showAttendanceDialog(lesson: Attendance) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun showExcuseDialog() fun openSummaryView() + fun openCalculatorView() + + fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) + fun startActionMode() fun showExcuseCheckboxes(show: Boolean) + fun showDayNavigation(show: Boolean) + fun finishActionMode() fun popView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt new file mode 100644 index 00000000..4b908bba --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt @@ -0,0 +1,67 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.databinding.ItemAttendanceCalculatorHeaderBinding +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +class AttendanceCalculatorAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemAttendanceCalculatorHeaderBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + override fun onBindViewHolder(parent: ViewHolder, position: Int) { + val context = parent.binding.root.context + val item = items[position] + + with(parent.binding) { + attendanceCalculatorPercentage.text = "${item.presencePercentage.roundToInt()}" + + attendanceCalculatorSummaryBalance.text = when { + item.lessonBalance > 0 -> { + context.getString( + R.string.attendance_calculator_summary_balance_positive, + item.lessonBalance + ) + } + + item.lessonBalance < 0 -> { + context.getString( + R.string.attendance_calculator_summary_balance_negative, + abs(item.lessonBalance) + ) + } + + else -> context.getString(R.string.attendance_calculator_summary_balance_neutral) + } + attendanceCalculatorWarning.isVisible = item.lessonBalance < 0 + attendanceCalculatorTitle.text = item.subjectName + attendanceCalculatorSummaryValues.text = if (item.total == 0) { + context.getString(R.string.attendance_calculator_summary_values_empty) + } else { + context.getString( + R.string.attendance_calculator_summary_values, + item.presences, + item.total + ) + } + } + } + + class ViewHolder(val binding: ItemAttendanceCalculatorHeaderBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt new file mode 100644 index 00000000..63d1d8be --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt @@ -0,0 +1,133 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +@AndroidEntryPoint +class AttendanceCalculatorFragment : + BaseFragment(R.layout.fragment_attendance_calculator), + AttendanceCalculatorView, MainView.TitledView { + + @Inject + lateinit var presenter: AttendanceCalculatorPresenter + + @Inject + lateinit var attendanceCalculatorAdapter: AttendanceCalculatorAdapter + + override val titleStringId get() = R.string.attendance_title + + companion object { + fun newInstance() = AttendanceCalculatorFragment() + } + + override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty() + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceCalculatorBinding.bind(view) + messageContainer = binding.attendanceCalculatorRecycler + presenter.onAttachView(this) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.action_menu_attendance_calculator, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.attendance_calculator_menu_settings) presenter.onSettingsSelected() + else false + } + + override fun openSettingsView() { + (activity as? MainActivity)?.pushView(AppearanceFragment.withFocusedPreference(getString(R.string.pref_key_attendance_target))) + } + + override fun initView() { + with(binding.attendanceCalculatorRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = attendanceCalculatorAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) + ) + attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun updateData(data: List) { + with(attendanceCalculatorAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearView() { + with(attendanceCalculatorAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showEmpty(show: Boolean) { + binding.attendanceCalculatorEmpty.isVisible = show + } + + override fun showErrorView(show: Boolean) { + binding.attendanceCalculatorError.isVisible = show + } + + override fun setErrorDetails(message: String) { + binding.attendanceCalculatorErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.attendanceCalculatorProgress.isVisible = show + } + + override fun enableSwipe(enable: Boolean) { + binding.attendanceCalculatorSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.attendanceCalculatorRecycler.isVisible = show + } + + override fun showRefresh(show: Boolean) { + binding.attendanceCalculatorSwipe.isRefreshing = show + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt new file mode 100644 index 00000000..29cb2197 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt @@ -0,0 +1,94 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class AttendanceCalculatorPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val getAttendanceCalculatorData: GetAttendanceCalculatorDataUseCase, +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: AttendanceCalculatorView) { + super.onAttachView(view) + view.initView() + Timber.i("Attendance calculator view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the attendance calculator") + loadData(forceRefresh = true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + getAttendanceCalculatorData(student, semester, forceRefresh) + } + .logResourceStatus("load attendance calculator") + .onResourceData { + view?.run { + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + fun onSettingsSelected(): Boolean { + view?.openSettingsView() + return true + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt new file mode 100644 index 00000000..21afe532 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.ui.base.BaseView + +interface AttendanceCalculatorView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun showRefresh(show: Boolean) + + fun showContent(show: Boolean) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun updateData(data: List) + + fun clearView() + + fun openSettingsView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt new file mode 100644 index 00000000..4e9f68c9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryAdapter.kt @@ -0,0 +1,109 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding +import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding +import io.github.wulkanowy.utils.calculatePercentage +import io.github.wulkanowy.utils.getFormattedName +import java.time.Month +import java.util.Locale +import javax.inject.Inject + +class AttendanceSummaryAdapter @Inject constructor( + private val preferencesRepository: PreferencesRepository +) : + RecyclerView.Adapter() { + + private val attendancePercentage = preferencesRepository.attendancePercentage + + private enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } + + var items = emptyList() + + override fun getItemCount() = if (items.isNotEmpty()) items.size + 2 else 0 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.HEADER.id + else -> ViewType.ITEM.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderAttendanceSummaryBinding.inflate(inflater, parent, false)) + ViewType.ITEM.id -> ItemViewHolder(ItemAttendanceSummaryBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 2) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) { + binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage( + attendancePercentage ?: + items.calculatePercentage() + ) + } + + private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) { + val item = if (position == -1) getTotalItem() else items[position] + + with(binding) { + attendanceSummaryMonth.text = when (position) { + -1 -> root.context.getString(R.string.attendance_summary_total) + else -> item.month.getFormattedName() + } + attendanceSummaryPercentage.text = when (position) { + -1 -> formatPercentage(attendancePercentage ?: item.calculatePercentage()) + else -> formatPercentage(attendancePercentage ?: item.calculatePercentage()) + } + + attendanceSummaryPresent.text = item.presence.toString() + attendanceSummaryAbsenceUnexcused.text = item.absence.toString() + attendanceSummaryAbsenceExcused.text = item.absenceExcused.toString() + attendanceSummaryAbsenceSchool.text = item.absenceForSchoolReasons.toString() + attendanceSummaryExemption.text = item.exemption.toString() + attendanceSummaryLatenessUnexcused.text = item.lateness.toString() + attendanceSummaryLatenessExcused.text = item.latenessExcused.toString() + } + } + + private fun getTotalItem() = AttendanceSummary( + month = Month.APRIL, + presence = items.sumOf { it.presence }, + absence = items.sumOf { it.absence }, + absenceExcused = items.sumOf { it.absenceExcused }, + absenceForSchoolReasons = items.sumOf { it.absenceForSchoolReasons }, + exemption = items.sumOf { it.exemption }, + lateness = items.sumOf { it.lateness }, + latenessExcused = items.sumOf { it.latenessExcused }, + diaryId = -1, + studentId = -1, + subjectId = -1 + ) + + private fun formatPercentage(percentage: Double): String { + return if (percentage == 0.0) "0%" + else "${String.format(Locale.FRANCE, "%.2f", percentage)}%" + } + + class HeaderViewHolder(val binding: ScrollableHeaderAttendanceSummaryBinding) : + RecyclerView.ViewHolder(binding.root) + + class ItemViewHolder(val binding: ItemAttendanceSummaryBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt index 8e30ea8b..e750b8d5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -1,32 +1,34 @@ package io.github.wulkanowy.ui.modules.attendance.summary import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.setOnItemSelectedListener -import kotlinx.android.synthetic.main.fragment_attendance_summary.* import javax.inject.Inject -class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainView.TitledView { +@AndroidEntryPoint +class AttendanceSummaryFragment : + BaseFragment(R.layout.fragment_attendance_summary), + AttendanceSummaryView, MainView.TitledView { @Inject lateinit var presenter: AttendanceSummaryPresenter @Inject - lateinit var attendanceSummaryAdapter: FlexibleAdapter> + lateinit var attendanceSummaryAdapter: AttendanceSummaryAdapter private lateinit var subjectsAdapter: ArrayAdapter @@ -36,44 +38,43 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie fun newInstance() = AttendanceSummaryFragment() } - override val totalString get() = getString(R.string.attendance_summary_total) - override val titleStringId get() = R.string.attendance_title - override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty + override val isViewEmpty get() = attendanceSummaryAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_attendance_summary, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = attendanceSummaryRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceSummaryBinding.bind(view) + messageContainer = binding.attendanceSummaryRecycler presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SUBJECT_KEY)) } override fun initView() { - with(attendanceSummaryRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.attendanceSummaryRecycler) { + layoutManager = LinearLayoutManager(context) adapter = attendanceSummaryAdapter } - attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) - attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() } - attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + attendanceSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) - with(attendanceSummarySubjects) { + with(binding.attendanceSummarySubjects) { adapter = subjectsAdapter setOnItemSelectedListener { presenter.onSubjectSelected(it?.text?.toString()) } } - attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) + binding.attendanceSummarySubjectsContainer.elevation = requireContext().dpToPx(1f) } - override fun updateSubjects(data: ArrayList) { + override fun updateSubjects(data: Collection) { with(subjectsAdapter) { clear() addAll(data) @@ -81,48 +82,50 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie } } - override fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) { + override fun updateDataSet(data: List) { with(attendanceSummaryAdapter) { - updateDataSet(data, true) - removeAllScrollableHeaders() - addScrollableHeader(header) + items = data + notifyDataSetChanged() } } override fun clearView() { - attendanceSummaryAdapter.clear() + with(attendanceSummaryAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { - attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - attendanceSummaryError.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - attendanceSummaryErrorMessage.text = message + binding.attendanceSummaryErrorMessage.text = message } override fun showProgress(show: Boolean) { - attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - attendanceSummarySwipe.isEnabled = enable + binding.attendanceSummarySwipe.isEnabled = enable } override fun showContent(show: Boolean) { - attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE + binding.attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE } override fun showSubjects(show: Boolean) { - attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE + binding.attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE } - override fun hideRefresh() { - attendanceSummarySwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.attendanceSummarySwipe.isRefreshing = show } override fun onSaveInstanceState(outState: Bundle) { @@ -131,7 +134,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt deleted file mode 100644 index 265d6ce4..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance.summary - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_attendance_summary.* - -class AttendanceSummaryItem( - private val month: String, - private val percentage: String, - private val present: String, - private val absence: String, - private val excusedAbsence: String, - private val schoolAbsence: String, - private val exemption: String, - private val lateness: String, - private val excusedLateness: String -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_attendance_summary - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - attendanceSummaryMonth.text = month - attendanceSummaryPercentage.text = percentage - attendanceSummaryPresent.text = present - attendanceSummaryAbsenceUnexcused.text = absence - attendanceSummaryAbsenceExcused.text = excusedAbsence - attendanceSummaryAbsenceSchool.text = schoolAbsence - attendanceSummaryExemption.text = exemption - attendanceSummaryLatenessUnexcused.text = lateness - attendanceSummaryLatenessExcused.text = excusedLateness - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceSummaryItem - - if (month != other.month) return false - if (percentage != other.percentage) return false - if (present != other.present) return false - if (absence != other.absence) return false - if (excusedAbsence != other.excusedAbsence) return false - if (schoolAbsence != other.schoolAbsence) return false - if (exemption != other.exemption) return false - if (lateness != other.lateness) return false - if (excusedLateness != other.excusedLateness) return false - - return true - } - - override fun hashCode(): Int { - var result = month.hashCode() - result = 31 * result + percentage.hashCode() - result = 31 * result + present.hashCode() - result = 31 * result + absence.hashCode() - result = 31 * result + excusedAbsence.hashCode() - result = 31 * result + schoolAbsence.hashCode() - result = 31 * result + exemption.hashCode() - result = 31 * result + lateness.hashCode() - result = 31 * result + excusedLateness.hashCode() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 8fc5b6e4..28199917 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -1,33 +1,27 @@ package io.github.wulkanowy.ui.modules.attendance.summary +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.subject.SubjectRepository +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.calculatePercentage -import io.github.wulkanowy.utils.getFormattedName -import org.threeten.bp.Month +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber -import java.lang.String.format -import java.util.Locale.FRANCE -import java.util.concurrent.TimeUnit.MILLISECONDS +import java.time.Month import javax.inject.Inject class AttendanceSummaryPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val attendanceSummaryRepository: AttendanceSummaryRepository, private val subjectRepository: SubjectRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var subjects = emptyList() @@ -78,41 +72,53 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { - Timber.i("Loading attendance summary data started") currentSubjectId = subjectId - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { - attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh) - } - } - .map { createAttendanceSummaryItems(it) to AttendanceSummaryScrollableHeader(formatPercentage(it.calculatePercentage())) } - .delay(200, MILLISECONDS) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading attendance summary result: Success") - view?.apply { - showEmpty(it.first.isEmpty()) - showContent(it.first.isNotEmpty()) - updateDataSet(it.first, it.second) - } - analytics.logEvent("load_attendance_summary", "items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId) - }) { - Timber.i("Loading attendance summary result: An exception occurred") - errorHandler.dispatch(it) - } + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = subjectId, + forceRefresh = forceRefresh ) } + .logResourceStatus("load attendance summary") + .mapResourceData(this::sortItems) + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateDataSet(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "attendance_summary", + "items" to it.size, + "item_id" to subjectId + ) + } + .onResourceNotLoading { + view?.run { + showProgress(false) + showRefresh(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun sortItems(items: List) = items.sortedByDescending { item -> + if (item.month.value <= Month.JUNE.value) item.month.value + 12 else item.month.value } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -127,65 +133,20 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadSubjects() { - Timber.i("Loading attendance summary subjects started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - subjectRepository.getSubjects(student, semester) - } - } - .doOnSuccess { subjects = it } - .map { ArrayList(it.map { subject -> subject.name }) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading attendance summary subjects result: Success") + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + subjectRepository.getSubjects(student, semester) + } + .logResourceStatus("load attendance summary subjects") + .onResourceData { + subjects = it view?.run { - view?.updateSubjects(it) + view?.updateSubjects(it.map { subject -> subject.name }.toList()) showSubjects(true) } - }, { - Timber.i("Loading attendance summary subjects result: An exception occurred") - errorHandler.dispatch(it) - }) - ) - } - - private fun createAttendanceSummaryTotalItem(attendanceSummary: List): AttendanceSummaryItem { - return AttendanceSummaryItem( - month = view?.totalString.orEmpty(), - percentage = formatPercentage(attendanceSummary.calculatePercentage()), - present = attendanceSummary.sumBy { it.presence }.toString(), - absence = attendanceSummary.sumBy { it.absence }.toString(), - excusedAbsence = attendanceSummary.sumBy { it.absenceExcused }.toString(), - schoolAbsence = attendanceSummary.sumBy { it.absenceForSchoolReasons }.toString(), - exemption = attendanceSummary.sumBy { it.exemption }.toString(), - lateness = attendanceSummary.sumBy { it.lateness }.toString(), - excusedLateness = attendanceSummary.sumBy { it.latenessExcused }.toString() - ) - } - - private fun createAttendanceSummaryItems(attendanceSummary: List): List { - if (attendanceSummary.isEmpty()) return emptyList() - return listOf(createAttendanceSummaryTotalItem(attendanceSummary)) + attendanceSummary.sortedByDescending { - if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value - }.map { - AttendanceSummaryItem( - month = it.month.getFormattedName(), - percentage = formatPercentage(it.calculatePercentage()), - present = it.presence.toString(), - absence = it.absence.toString(), - excusedAbsence = it.absenceExcused.toString(), - schoolAbsence = it.absenceForSchoolReasons.toString(), - exemption = it.exemption.toString(), - lateness = it.lateness.toString(), - excusedLateness = it.latenessExcused.toString() - ) - } - } - - private fun formatPercentage(percentage: Double): String { - return if (percentage == 0.0) "0%" - else "${format(FRANCE, "%.2f", percentage)}%" + } + .onResourceError(errorHandler::dispatch) + .launch("subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt deleted file mode 100644 index c258f71d..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.wulkanowy.ui.modules.attendance.summary - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.scrollable_header_attendance_summary.* - -class AttendanceSummaryScrollableHeader(private val percentage: String) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.scrollable_header_attendance_summary - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, position: Int, payloads: MutableList?) { - holder?.apply { attendanceSummaryScrollableHeaderPercentage.text = percentage } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttendanceSummaryScrollableHeader - - if (percentage != other.percentage) return false - - return true - } - - override fun hashCode(): Int { - return percentage.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt index 8bd5332d..99192f18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt @@ -1,16 +1,15 @@ package io.github.wulkanowy.ui.modules.attendance.summary +import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.ui.base.BaseView interface AttendanceSummaryView : BaseView { - val totalString: String - val isViewEmpty: Boolean fun initView() - fun hideRefresh() + fun showRefresh(show: Boolean) fun showContent(show: Boolean) @@ -24,9 +23,9 @@ interface AttendanceSummaryView : BaseView { fun setErrorDetails(message: String) - fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) + fun updateDataSet(data: List) - fun updateSubjects(data: ArrayList) + fun updateSubjects(data: Collection) fun showSubjects(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt new file mode 100644 index 00000000..0f7c4234 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt @@ -0,0 +1,86 @@ +package io.github.wulkanowy.ui.modules.auth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogAuthBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import javax.inject.Inject + +@AndroidEntryPoint +class AuthDialog : BaseDialogFragment(), AuthView { + + @Inject + lateinit var presenter: AuthPresenter + + companion object { + fun newInstance() = AuthDialog() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.FullScreenDialogStyle) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return DialogAuthBinding.inflate(inflater).apply { binding = this }.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + presenter.onAttachView(this) + + binding.authInput.doOnTextChanged { text, _, _, _ -> + presenter.onPeselChange(text?.toString()) + } + + binding.authButton.setOnClickListener { presenter.authorize() } + binding.authSuccessButton.setOnClickListener { + activity?.recreate() + dismiss() + } + binding.authButtonSkip.setOnClickListener { dismiss() } + } + + override fun enableAuthButton(isEnabled: Boolean) { + binding.authButton.isEnabled = isEnabled + } + + override fun showProgress(show: Boolean) { + binding.authProgress.isVisible = show + } + + override fun showPeselError(show: Boolean) { + binding.authInputLayout.error = getString(R.string.auth_api_error).takeIf { show } + } + + override fun showInvalidPeselError(show: Boolean) { + binding.authInputLayout.error = getString(R.string.auth_invalid_error).takeIf { show } + } + + override fun showSuccess(show: Boolean) { + binding.authSuccess.isVisible = show + } + + override fun showContent(show: Boolean) { + binding.authForm.isVisible = show + } + + override fun showDescriptionWithName(name: String) { + binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml() + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt new file mode 100644 index 00000000..0be086b6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt @@ -0,0 +1,111 @@ +package io.github.wulkanowy.ui.modules.auth + +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +class AuthPresenter @Inject constructor( + private val semesterRepository: SemesterRepository, + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var pesel: String = "" + + override fun onAttachView(view: AuthView) { + super.onAttachView(view) + view.enableAuthButton(pesel.length == 11) + view.showSuccess(false) + view.showProgress(false) + + loadName() + } + + private fun loadName() { + presenterScope.launch { + runCatching { + studentRepository.getCurrentStudent(false) + .studentName + .replace(" ", "\u00A0") + } + .onSuccess { view?.showDescriptionWithName(it) } + .onFailure { errorHandler.dispatch(it) } + } + } + + fun onPeselChange(newPesel: String?) { + pesel = newPesel.orEmpty() + + view?.enableAuthButton(pesel.length == 11) + view?.showPeselError(false) + view?.showInvalidPeselError(false) + } + + fun authorize() { + presenterScope.launch { + view?.showProgress(true) + view?.showContent(false) + + if (!isValidPESEL(pesel)) { + view?.showInvalidPeselError(true) + view?.showProgress(false) + view?.showContent(true) + return@launch + } + + runCatching { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + + val isSuccess = studentRepository.authorizePermission(student, semester, pesel) + Timber.d("Auth succeed: $isSuccess") + if (isSuccess) { + studentRepository.refreshStudentAfterAuthorize(student, semester) + } + isSuccess + } + .onFailure { + errorHandler.dispatch(it) + view?.showProgress(false) + view?.showContent(true) + } + .onSuccess { + Timber.d("Auth fully succeed: $it") + if (it) { + view?.showSuccess(true) + view?.showContent(false) + view?.showPeselError(false) + } else { + view?.showSuccess(false) + view?.showContent(true) + view?.showPeselError(true) + } + } + + view?.showProgress(false) + } + } + + private fun isValidPESEL(peselString: String): Boolean { + if (peselString.length != 11) { + return false + } + + val weights = intArrayOf(1, 3, 7, 9, 1, 3, 7, 9, 1, 3) + var sum = 0 + + for (i in 0 until 10) { + sum += weights[i] * Character.getNumericValue(peselString[i]) + } + + sum %= 10 + sum = 10 - sum + sum %= 10 + + return sum == Character.getNumericValue(peselString[10]) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt new file mode 100644 index 00000000..d7e1917c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.ui.modules.auth + +import io.github.wulkanowy.ui.base.BaseView + +interface AuthView : BaseView { + + fun enableAuthButton(isEnabled: Boolean) + + fun showProgress(show: Boolean) + + fun showPeselError(show: Boolean) + + fun showInvalidPeselError(show: Boolean) + + fun showSuccess(show: Boolean) + + fun showContent(show: Boolean) + + fun showDescriptionWithName(name: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt new file mode 100644 index 00000000..922b652b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -0,0 +1,91 @@ +package io.github.wulkanowy.ui.modules.captcha + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.core.os.bundleOf +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.databinding.DialogCaptchaBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.WebkitCookieManagerProxy +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class CaptchaDialog : BaseDialogFragment() { + + @Inject + lateinit var wulkanowySdkFactory: WulkanowySdkFactory + + @Inject + lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy + + private var webView: WebView? = null + + companion object { + const val CAPTCHA_SUCCESS = "captcha_success" + private const val CAPTCHA_URL = "captcha_url" + private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null" + + fun newInstance(url: String?): CaptchaDialog { + return CaptchaDialog().apply { + arguments = bundleOf(CAPTCHA_URL to url) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root + + @SuppressLint("SetJavaScriptEnabled") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + isCancelable = false + binding.captchaRefresh.setOnClickListener { + binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) + } + binding.captchaClose.setOnClickListener { dismiss() } + + with(binding.captchaWebview) { + webView = this + with(settings) { + javaScriptEnabled = true + userAgentString = wulkanowySdkFactory.createBase().userAgent + } + + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + view?.evaluateJavascript(CAPTCHA_CHECK_JS) { + if (it == "true") { + onChallengeAccepted() + } + } + } + } + + loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) + } + } + + private fun onChallengeAccepted() { + runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) } + .onFailure { Timber.e(it) } + showMessage(getString(R.string.captcha_verified_message)) + dismissAllowingStateLoss() + } + + override fun onDestroy() { + webkitCookieManagerProxy.webkitCookieManager?.flush() + webView?.destroy() + super.onDestroy() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt new file mode 100644 index 00000000..f63b293c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt @@ -0,0 +1,41 @@ +package io.github.wulkanowy.ui.modules.conference + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.ItemConferenceBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class ConferenceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onItemClickListener: (Conference) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemConferenceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + with(holder.binding) { + conferenceItemDate.text = item.date.toFormattedString("dd.MM.yyyy HH:mm") + conferenceItemName.text = item.presentOnConference + conferenceItemTitle.text = item.title + conferenceItemSubject.text = item.subject + conferenceItemContent.text = item.agenda + conferenceItemContent.visibility = + if (item.agenda.isBlank()) View.GONE else View.VISIBLE + + root.setOnClickListener { onItemClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemConferenceBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt new file mode 100644 index 00000000..c532377e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.modules.conference + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.DialogConferenceBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.serializable +import io.github.wulkanowy.utils.toFormattedString + +@AndroidEntryPoint +class ConferenceDialog : BaseDialogFragment() { + + private lateinit var conference: Conference + + companion object { + + private const val ARGUMENT_KEY = "item" + + fun newInstance(conference: Conference) = ConferenceDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to conference) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + conference = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogConferenceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + conferenceDialogClose.setOnClickListener { dismiss() } + + conferenceDialogSubjectValue.text = conference.subject + conferenceDialogDateValue.text = conference.date.toFormattedString("dd.MM.yyyy HH:mm") + conferenceDialogHeaderValue.text = conference.title + conferenceDialogAgendaValue.text = conference.agenda + conferenceDialogPresentValue.text = conference.presentOnConference + conferenceDialogPresentValue.isVisible = conference.presentOnConference.isNotBlank() + conferenceDialogPresentTitle.isVisible = conference.presentOnConference.isNotBlank() + conferenceDialogAgendaValue.isVisible = conference.agenda.isNotBlank() + conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank() + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt new file mode 100644 index 00000000..0cd3150c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt @@ -0,0 +1,124 @@ +package io.github.wulkanowy.ui.modules.conference + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.FragmentConferenceBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +@AndroidEntryPoint +class ConferenceFragment : BaseFragment(R.layout.fragment_conference), + ConferenceView, MainView.TitledView, MainView.MainChildView { + + @Inject + lateinit var presenter: ConferencePresenter + + @Inject + lateinit var conferencesAdapter: ConferenceAdapter + + companion object { + fun newInstance() = ConferenceFragment() + } + + override val isViewEmpty: Boolean + get() = conferencesAdapter.items.isEmpty() + + override val titleStringId: Int + get() = R.string.conferences_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentConferenceBinding.bind(view) + messageContainer = binding.conferenceRecycler + presenter.onAttachView(this) + } + + override fun initView() { + conferencesAdapter.onItemClickListener = presenter::onItemSelected + + with(binding.conferenceRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = conferencesAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + conferenceSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) + ) + conferenceErrorRetry.setOnClickListener { presenter.onRetry() } + conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun updateData(data: List) { + with(conferencesAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(conferencesAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showRefresh(show: Boolean) { + binding.conferenceSwipe.isRefreshing = show + } + + override fun showProgress(show: Boolean) { + binding.conferenceProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showEmpty(show: Boolean) { + binding.conferenceEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.conferenceError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.conferenceErrorMessage.text = message + } + + override fun enableSwipe(enable: Boolean) { + binding.conferenceSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun openConferenceDialog(conference: Conference) { + (activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference)) + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.conferenceRecycler.smoothScrollToPosition(0) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt new file mode 100644 index 00000000..1178c720 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -0,0 +1,106 @@ +package io.github.wulkanowy.ui.modules.conference + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import timber.log.Timber +import javax.inject.Inject + +class ConferencePresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val conferenceRepository: ConferenceRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: ConferenceView) { + super.onAttachView(view) + view.initView() + Timber.i("Conferences view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + loadData(true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(true) + } + + fun onItemSelected(conference: Conference) { + view?.openConferenceDialog(conference) + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + conferenceRepository.getConferences(student, semester, forceRefresh) + } + .logResourceStatus("load conference data") + .mapResourceData { it.sortedByDescending { conference -> conference.date } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "conferences", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + fun onFragmentReselected() { + Timber.i("Conference is reselected") + if (view?.isViewEmpty == false) { + view?.resetView() + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt new file mode 100644 index 00000000..3299a1f0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.ui.modules.conference + +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.ui.base.BaseView + +interface ConferenceView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun showRefresh(show: Boolean) + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) + + fun openConferenceDialog(conference: Conference) + + fun resetView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt new file mode 100644 index 00000000..b7a0796c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -0,0 +1,244 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentDashboardBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment +import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS +import io.github.wulkanowy.ui.modules.conference.ConferenceFragment +import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder +import io.github.wulkanowy.ui.modules.exam.ExamFragment +import io.github.wulkanowy.ui.modules.grade.GradeFragment +import io.github.wulkanowy.ui.modules.homework.HomeworkFragment +import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.message.MessageFragment +import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment +import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment +import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getErrorString +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class DashboardFragment : BaseFragment(R.layout.fragment_dashboard), + DashboardView, MainView.TitledView, MainView.MainChildView { + + @Inject + lateinit var presenter: DashboardPresenter + + @Inject + lateinit var dashboardAdapter: DashboardAdapter + + override val titleStringId get() = R.string.dashboard_title + + override var subtitleString = + LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise() + + override val tileWidth: Int + get() { + val recyclerWidth = binding.dashboardRecycler.width + val margin = requireContext().dpToPx(24f).toInt() + + return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt() + } + + override val isViewEmpty + get() = dashboardAdapter.itemCount == 0 + + companion object { + + fun newInstance() = DashboardFragment() + } + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentDashboardBinding.bind(view) + presenter.onAttachView(this) + initializeCaptchaResultObserver() + } + + private fun initializeCaptchaResultObserver() { + childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ -> + presenter.onRetryAfterCaptcha() + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.action_menu_dashboard, menu) + } + + override fun initView() { + val mainActivity = requireActivity() as MainActivity + val itemTouchHelper = ItemTouchHelper( + DashboardItemMoveCallback(dashboardAdapter, presenter::onDragAndDropEnd) + ) + + dashboardAdapter.apply { + onAccountTileClickListener = { + mainActivity.pushView(AccountDetailsFragment.newInstance(it)) + } + onLuckyNumberTileClickListener = { + mainActivity.pushView(LuckyNumberFragment.newInstance()) + } + onMessageTileClickListener = { mainActivity.pushView(MessageFragment.newInstance()) } + onAttendanceTileClickListener = { + mainActivity.pushView(AttendanceSummaryFragment.newInstance()) + } + onLessonsTileClickListener = { + mainActivity.pushView(TimetableFragment.newInstance(it)) + } + onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) } + onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) } + onAnnouncementsTileClickListener = { + mainActivity.pushView(SchoolAnnouncementFragment.newInstance()) + } + onExamsTileClickListener = { mainActivity.pushView(ExamFragment.newInstance()) } + onConferencesTileClickListener = { + mainActivity.pushView(ConferenceFragment.newInstance()) + } + onAdminMessageClickListener = presenter::onAdminMessageSelected + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed + + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + binding.dashboardRecycler.scrollToPosition(0) + } + }) + } + + with(binding) { + dashboardErrorRetry.setOnClickListener { presenter.onRetry() } + dashboardErrorDetails.setOnClickListener { presenter.onDetailsClick() } + dashboardSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + dashboardSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + dashboardSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + + with(dashboardRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = dashboardAdapter + (itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false + } + + itemTouchHelper.attachToRecyclerView(dashboardRecycler) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected() + R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected() + else -> false + } + } + + override fun showDashboardTileSettings(selectedItems: List) { + val entries = requireContext().resources.getStringArray(R.array.dashboard_tile_entries) + val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values) + val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.pref_dashboard_appearance_tiles_title) + .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } + .setPositiveButton(android.R.string.ok) { dialog, _ -> + val selectedState = (dialog as AlertDialog).listView.checkedItemPositions + val selectedValues = values.filterIndexed { index, _ -> selectedState[index] } + + presenter.onDashboardTileSettingSelected(selectedValues) + } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + + override fun updateData(data: List) { + dashboardAdapter.submitList(data.toMutableList()) + } + + override fun showMessage(text: String) { + //Empty function to avoid message flood + } + + override fun showRefresh(show: Boolean) { + binding.dashboardSwipe.isRefreshing = show + } + + override fun showProgress(show: Boolean) { + binding.dashboardProgress.isVisible = show + } + + override fun showContent(show: Boolean) { + binding.dashboardRecycler.isVisible = show + } + + override fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages?) { + binding.dashboardErrorContainer.isVisible = show + binding.dashboardErrorAdminMessage.root.isVisible = adminMessageItem != null + + if (adminMessageItem != null) { + AdminMessageViewHolder( + binding = binding.dashboardErrorAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessageItem.adminMessage) + } + } + + override fun setErrorDetails(error: Throwable) { + binding.dashboardErrorMessage.text = requireContext().resources.getErrorString(error) + } + + override fun resetView() { + binding.dashboardRecycler.smoothScrollToPosition(0) + } + + override fun popViewToRoot() { + (requireActivity() as MainActivity).popView(20) + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun openNotificationsCenterView() { + (requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance()) + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } + + override fun onDestroyView() { + dashboardAdapter.clearTimers() + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt new file mode 100644 index 00000000..506f6124 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt @@ -0,0 +1,176 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import io.github.wulkanowy.data.db.entities.* +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.pojos.TimetableFull +import io.github.wulkanowy.utils.AdBanner +import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework + +sealed class DashboardItem(val type: Type) { + + abstract val error: Throwable? + + abstract val isLoading: Boolean + + abstract val isDataLoaded: Boolean + + data class AdminMessages( + val adminMessage: AdminMessage? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.ADMIN_MESSAGE) { + + override val isDataLoaded get() = adminMessage != null + } + + data class Account( + val student: Student? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.ACCOUNT) { + + override val isDataLoaded get() = student != null + } + + data class HorizontalGroup( + val unreadMessagesCount: Cell? = null, + val attendancePercentage: Cell? = null, + val luckyNumber: Cell? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.HORIZONTAL_GROUP) { + + data class Cell( + val data: T?, + val error: Boolean, + val isLoading: Boolean, + ) { + val isHidden: Boolean + get() = data == null && !error && !isLoading + } + + override val isDataLoaded + get() = unreadMessagesCount?.isLoading == false || attendancePercentage?.isLoading == false || luckyNumber?.isLoading == false + + val isFullDataLoaded + get() = luckyNumber?.isLoading != true && attendancePercentage?.isLoading != true && unreadMessagesCount?.isLoading != true + } + + data class Grades( + val subjectWithGrades: Map>? = null, + val gradeTheme: GradeColorTheme? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.GRADES) { + + override val isDataLoaded get() = subjectWithGrades != null + } + + data class Lessons( + val lessons: TimetableFull? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.LESSONS) { + + override val isDataLoaded get() = lessons != null + } + + data class Homework( + val homework: List? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.HOMEWORK) { + + override val isDataLoaded get() = homework != null + } + + data class Announcements( + val announcement: List? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.ANNOUNCEMENTS) { + + override val isDataLoaded get() = announcement != null + } + + data class Exams( + val exams: List? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.EXAMS) { + + override val isDataLoaded get() = exams != null + } + + data class Conferences( + val conferences: List? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.CONFERENCES) { + + override val isDataLoaded get() = conferences != null + } + + data class Ads( + val adBanner: AdBanner? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.ADS) { + + override val isDataLoaded get() = adBanner != null + } + + enum class Type { + ADMIN_MESSAGE, + ACCOUNT, + HORIZONTAL_GROUP, + LESSONS, + ADS, + GRADES, + HOMEWORK, + ANNOUNCEMENTS, + EXAMS, + CONFERENCES, + } + + enum class Tile { + ADMIN_MESSAGE, + ACCOUNT, + LUCKY_NUMBER, + MESSAGES, + ATTENDANCE, + LESSONS, + ADS, + GRADES, + HOMEWORK, + ANNOUNCEMENTS, + EXAMS, + CONFERENCES, + } + + enum class HiddenAttendanceTile { + UNEXCUSED_ABSENCE, + EXEMPTION, + EXCUSED_LATENESS, + UNEXCUSED_LATENESS, + PRESENT, + DELETED, + EXCUSED_ABSENCE, + UNKNOWN, + } +} + +fun DashboardItem.Tile.toDashboardItemType() = when (this) { + DashboardItem.Tile.ADMIN_MESSAGE -> DashboardItem.Type.ADMIN_MESSAGE + DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT + DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP + DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP + DashboardItem.Tile.ATTENDANCE -> DashboardItem.Type.HORIZONTAL_GROUP + DashboardItem.Tile.LESSONS -> DashboardItem.Type.LESSONS + DashboardItem.Tile.GRADES -> DashboardItem.Type.GRADES + DashboardItem.Tile.HOMEWORK -> DashboardItem.Type.HOMEWORK + DashboardItem.Tile.ANNOUNCEMENTS -> DashboardItem.Type.ANNOUNCEMENTS + DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS + DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES + DashboardItem.Tile.ADS -> DashboardItem.Type.ADS +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt new file mode 100644 index 00000000..f033b594 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt @@ -0,0 +1,60 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder +import java.util.* + +class DashboardItemMoveCallback( + private val dashboardAdapter: DashboardAdapter, + private var onUserInteractionEndListener: (List) -> Unit = {} +) : ItemTouchHelper.Callback() { + + override fun isLongPressDragEnabled() = true + + override fun isItemViewSwipeEnabled() = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + //Not implemented + } + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + val dragFlags = if (!viewHolder.isAdminMessageOrAccountItem) { + ItemTouchHelper.UP or ItemTouchHelper.DOWN + } else 0 + + return makeMovementFlags(dragFlags, 0) + } + + override fun canDropOver( + recyclerView: RecyclerView, + current: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ) = !target.isAdminMessageOrAccountItem + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val list = dashboardAdapter.items.toMutableList() + + Collections.swap(list, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + + dashboardAdapter.submitList(list) + return true + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + onUserInteractionEndListener(dashboardAdapter.items.toList()) + } + + private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean + get() = this is AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt new file mode 100644 index 00000000..9ba71937 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -0,0 +1,904 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.errorOrNull +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase +import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AdsHelper +import io.github.wulkanowy.utils.calculatePercentage +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.sunday +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import timber.log.Timber +import java.time.Instant +import java.time.LocalDate +import javax.inject.Inject + +class DashboardPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val luckyNumberRepository: LuckyNumberRepository, + private val gradeRepository: GradeRepository, + private val semesterRepository: SemesterRepository, + private val messageRepository: MessageRepository, + private val attendanceSummaryRepository: AttendanceSummaryRepository, + private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, + private val homeworkRepository: HomeworkRepository, + private val examRepository: ExamRepository, + private val conferenceRepository: ConferenceRepository, + private val preferencesRepository: PreferencesRepository, + private val schoolAnnouncementRepository: SchoolAnnouncementRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, + private val adsHelper: AdsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private val dashboardItemLoadedList = mutableListOf() + + private val dashboardItemRefreshLoadedList = mutableListOf() + + private var dashboardItemsToLoad = emptySet() + + private var dashboardTileLoadedList = emptySet() + + private val firstLoadedItemList = mutableListOf() + + private val selectedDashboardTiles + get() = preferencesRepository.selectedDashboardTiles + .filterNot { it == DashboardItem.Tile.ADS && !adsHelper.canShowAd } + .toSet() + + private lateinit var lastError: Throwable + + override fun onAttachView(view: DashboardView) { + super.onAttachView(view) + + with(view) { + initView() + showProgress(true) + showContent(false) + } + + val selectedDashboardTilesFlow = preferencesRepository.selectedDashboardTilesFlow + .map { selectedDashboardTiles } + val isAdsEnabledFlow = preferencesRepository.isAdsEnabledFlow + .filter { (adsHelper.canShowAd && it) || !it } + .map { selectedDashboardTiles } + val isMobileAdsSdkInitializedFlow = adsHelper.isMobileAdsSdkInitialized + .filter { it } + .map { selectedDashboardTiles } + + merge( + selectedDashboardTilesFlow, + isAdsEnabledFlow, + isMobileAdsSdkInitializedFlow + ) + .onEach { loadData(tilesToLoad = it) } + .launch("dashboard_pref") + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + loadData(selectedDashboardTiles) + } + + fun onDragAndDropEnd(list: List) { + with(dashboardItemLoadedList) { + clear() + addAll(list) + } + + val positionList = + list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap() + + preferencesRepository.dashboardItemsPosition = positionList + } + + fun loadData( + tilesToLoad: Set, + forceRefresh: Boolean = false, + ) { + val oldDashboardTileLoadedList = dashboardTileLoadedList + dashboardItemsToLoad = tilesToLoad.map { it.toDashboardItemType() }.toSet() + dashboardTileLoadedList = tilesToLoad + + val itemsToLoad = generateDashboardTileListToLoad( + dashboardTilesToLoad = tilesToLoad, + dashboardLoadedTiles = oldDashboardTileLoadedList, + forceRefresh = forceRefresh + ).map { it.toDashboardItemType() } + + removeUnselectedTiles(tilesToLoad.toList()) + loadTiles(tileList = itemsToLoad, forceRefresh = forceRefresh) + } + + private fun generateDashboardTileListToLoad( + dashboardTilesToLoad: Set, + dashboardLoadedTiles: Set, + forceRefresh: Boolean + ) = dashboardTilesToLoad.filter { newItemToLoad -> + dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh + || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE + } + + private fun removeUnselectedTiles(tilesToLoad: List) { + dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } } + + val horizontalGroup = + dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup? + + if (horizontalGroup != null) { + val isLuckyNumberToLoad = DashboardItem.Tile.LUCKY_NUMBER in tilesToLoad + val isMessagesToLoad = DashboardItem.Tile.MESSAGES in tilesToLoad + val isAttendanceToLoad = DashboardItem.Tile.ATTENDANCE in tilesToLoad + + val horizontalGroupIndex = dashboardItemLoadedList.indexOf(horizontalGroup) + + val newHorizontalGroup = horizontalGroup.copy( + attendancePercentage = horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad }, + unreadMessagesCount = horizontalGroup.unreadMessagesCount.takeIf { isMessagesToLoad }, + luckyNumber = horizontalGroup.luckyNumber.takeIf { isLuckyNumberToLoad } + ) + + with(dashboardItemLoadedList) { + removeAt(horizontalGroupIndex) + add(horizontalGroupIndex, newHorizontalGroup) + } + } + + view?.updateData(dashboardItemLoadedList) + } + + private fun loadTiles( + tileList: List, + forceRefresh: Boolean + ) { + presenterScope.launch { + Timber.i("Loading dashboard account data started") + val student = runCatching { studentRepository.getCurrentStudent(true) } + .onFailure { + Timber.i("Loading dashboard account result: An exception occurred") + errorHandler.dispatch(it) + updateData(DashboardItem.Account(error = it), forceRefresh) + } + .onSuccess { Timber.i("Loading dashboard account result: Success") } + .getOrNull() ?: return@launch + + tileList.forEach { + when (it) { + DashboardItem.Type.ACCOUNT -> { + updateData(DashboardItem.Account(student), forceRefresh) + } + + DashboardItem.Type.HORIZONTAL_GROUP -> { + loadHorizontalGroup(student, forceRefresh) + } + + DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh) + DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh) + DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh) + DashboardItem.Type.ANNOUNCEMENTS -> { + loadSchoolAnnouncements(student, forceRefresh) + } + + DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh) + DashboardItem.Type.CONFERENCES -> { + loadConferences(student, forceRefresh) + } + + DashboardItem.Type.ADS -> loadAds(forceRefresh) + DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh) + } + } + } + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the dashboard") + loadData(selectedDashboardTiles, forceRefresh = true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(selectedDashboardTiles, forceRefresh = true) + } + + fun onRetryAfterCaptcha() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(selectedDashboardTiles, forceRefresh = true) + } + + fun onViewReselected() { + Timber.i("Dashboard view is reselected") + view?.run { + resetView() + popViewToRoot() + } + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + fun onNotificationsCenterSelected(): Boolean { + view?.openNotificationsCenterView() + return true + } + + fun onDashboardTileSettingsSelected(): Boolean { + view?.showDashboardTileSettings(selectedDashboardTiles.toList()) + return true + } + + fun onDashboardTileSettingSelected(selectedItems: List) { + preferencesRepository.selectedDashboardTiles = selectedItems.map { + DashboardItem.Tile.valueOf(it) + }.toSet() + } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { + flow { + val attendancePercentage = preferencesRepository.attendancePercentage + + val selectedTiles = selectedDashboardTiles + val flowSuccess = flowOf(Resource.Success(null)) + + val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) + .mapResourceData { + it ?: LuckyNumber(0, LocalDate.now(), 0) + } + .onResourceError { errorHandler.dispatch(it) } + .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess + + val messageFLow = flatResourceFlow { + val mailbox = messageRepository.getMailboxByStudent(student) + + messageRepository.getMessages( + student = student, + mailbox = mailbox, + folder = MessageFolder.RECEIVED, + forceRefresh = forceRefresh + ) + } + .mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } } + .onResourceError { errorHandler.dispatch(it) } + .takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess + + val attendanceFlow = flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = -1, + forceRefresh = forceRefresh + ) + } + .onResourceError { errorHandler.dispatch(it) } + .takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess + + emitAll( + combine( + flow = luckyNumberFlow, + flow2 = messageFLow, + flow3 = attendanceFlow, + ) { luckyNumberResource, messageResource, attendanceResource -> + val resList = listOf(luckyNumberResource, messageResource, attendanceResource) + + resList to DashboardItem.HorizontalGroup( + isLoading = resList.any { it is Resource.Loading }, + error = resList.map { it.errorOrNull }.let { errors -> + if (errors.all { it != null }) { + errors.firstOrNull() + } else null + }, + attendancePercentage = DashboardItem.HorizontalGroup.Cell( + data = attendancePercentage ?: attendanceResource.dataOrNull?.calculatePercentage(), + error = attendanceResource.errorOrNull != null, + isLoading = attendanceResource is Resource.Loading, + ), + unreadMessagesCount = DashboardItem.HorizontalGroup.Cell( + data = messageResource.dataOrNull?.count { it.unread }, + error = messageResource.errorOrNull != null, + isLoading = messageResource is Resource.Loading, + ), + luckyNumber = DashboardItem.HorizontalGroup.Cell( + data = luckyNumberResource.dataOrNull?.luckyNumber, + error = luckyNumberResource.errorOrNull != null, + isLoading = luckyNumberResource is Resource.Loading, + ) + ) + }) + } + .filterNot { (_, it) -> it.isLoading && forceRefresh } + .distinctUntilChanged() + .onEach { (_, it) -> + updateData(it, forceRefresh) + + if (it.isLoading) { + Timber.i("Loading horizontal group data started") + } else { + firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP + Timber.i("Loading horizontal group result: Success") + } + } + .catch { + Timber.i("Loading horizontal group result: An exception occurred") + updateData( + DashboardItem.HorizontalGroup(error = it), + forceRefresh, + ) + errorHandler.dispatch(it) + } + .launchWithUniqueRefreshJob("horizontal_group", forceRefresh) + } + + private fun loadGrades(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + + gradeRepository.getGrades(student, semester, forceRefresh) + } + .mapResourceData { (details, _) -> + val filteredSubjectWithGrades = details + .filter { it.date >= LocalDate.now().minusDays(7) } + .groupBy { it.subject } + .mapValues { entry -> + entry.value + .take(5) + .sortedByDescending { it.date } + } + .toList() + .sortedByDescending { (_, grades) -> grades[0].date } + .toMap() + + filteredSubjectWithGrades + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard grades data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Grades( + subjectWithGrades = it.dataOrNull, + gradeTheme = preferencesRepository.gradeColorTheme, + isLoading = true + ), false + ) + + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.GRADES + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard grades result: Success") + updateData( + DashboardItem.Grades( + subjectWithGrades = it.data, + gradeTheme = preferencesRepository.gradeColorTheme + ), + forceRefresh + ) + } + + is Resource.Error -> { + Timber.i("Loading dashboard grades result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Grades(error = it.error), forceRefresh) + } + } + } + .launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) + } + + private fun loadLessons(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) { + true -> LocalDate.now() + else -> LocalDate.now().nextOrSameSchoolDay + } + + timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date.sunday, + forceRefresh = forceRefresh, + ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard lessons data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Lessons(it.dataOrNull, isLoading = true), + false + ) + + if (!it.dataOrNull?.lessons.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.LESSONS + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard lessons result: Success") + updateData( + DashboardItem.Lessons(it.data), forceRefresh + ) + } + + is Resource.Error -> { + Timber.i("Loading dashboard lessons result: An exception occurred") + errorHandler.dispatch(it.error) + updateData( + DashboardItem.Lessons(error = it.error), forceRefresh + ) + } + } + } + .launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) + } + + private fun loadHomework(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + val date = LocalDate.now().nextOrSameSchoolDay + + homeworkRepository.getHomework( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = forceRefresh + ) + } + .mapResourceData { homework -> + val currentDate = LocalDate.now() + + val filteredHomework = homework.filter { + (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone + }.sortedBy { it.date } + + filteredHomework + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard homework data started") + if (forceRefresh) return@onEach + val data = it.dataOrNull.orEmpty() + updateData( + DashboardItem.Homework(data, isLoading = true), + false + ) + + if (data.isNotEmpty()) { + firstLoadedItemList += DashboardItem.Type.HOMEWORK + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard homework result: Success") + updateData(DashboardItem.Homework(it.data), forceRefresh) + } + + is Resource.Error -> { + Timber.i("Loading dashboard homework result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Homework(error = it.error), forceRefresh) + } + } + } + .launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) + } + + private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard announcements data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true), + false + ) + + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard announcements result: Success") + updateData(DashboardItem.Announcements(it.data), forceRefresh) + } + + is Resource.Error -> { + Timber.i("Loading dashboard announcements result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Announcements(error = it.error), forceRefresh) + } + } + } + .launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) + } + + private fun loadExams(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + + examRepository.getExams( + student = student, + semester = semester, + start = LocalDate.now(), + end = LocalDate.now().plusDays(7), + forceRefresh = forceRefresh + ) + } + .mapResourceData { exams -> exams.sortedBy { exam -> exam.date } } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard exams data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true), + false + ) + + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.EXAMS + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard exams result: Success") + updateData(DashboardItem.Exams(it.data), forceRefresh) + } + + is Resource.Error -> { + Timber.i("Loading dashboard exams result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Exams(error = it.error), forceRefresh) + } + } + } + .launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) + } + + private fun loadConferences(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + + conferenceRepository.getConferences( + student = student, + semester = semester, + forceRefresh = forceRefresh, + startDate = Instant.now(), + ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard conferences data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true), + false + ) + + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.CONFERENCES + } + } + + is Resource.Success -> { + Timber.i("Loading dashboard conferences result: Success") + updateData(DashboardItem.Conferences(it.data), forceRefresh) + } + + is Resource.Error -> { + Timber.i("Loading dashboard conferences result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Conferences(error = it.error), forceRefresh) + } + } + } + .launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) + } + + private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + student = student, + type = MessageType.DASHBOARD_MESSAGE, + ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard admin message data started") + if (forceRefresh) return@onEach + updateData(DashboardItem.AdminMessages(), false) + } + + is Resource.Success -> { + Timber.i("Loading dashboard admin message result: Success") + updateData( + dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data), + forceRefresh = forceRefresh + ) + } + + is Resource.Error -> { + Timber.i("Loading dashboard admin message result: An exception occurred") + Timber.e(it.error) + updateData( + dashboardItem = DashboardItem.AdminMessages( + adminMessage = null, + error = it.error + ), + forceRefresh = forceRefresh + ) + } + } + } + .launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh) + } + + private fun loadAds(forceRefresh: Boolean) { + presenterScope.launch { + if (!forceRefresh) { + updateData(DashboardItem.Ads(), false) + } + + val dashboardAdItem = + runCatching { + DashboardItem.Ads(adsHelper.getDashboardTileAdBanner(view!!.tileWidth)) + } + .onFailure { Timber.e(it) } + .getOrElse { DashboardItem.Ads(error = it) } + + updateData(dashboardAdItem, forceRefresh) + } + } + + private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { + val isForceRefreshError = forceRefresh && dashboardItem.error != null + val isFirstRunDataLoadedError = + dashboardItem.type in firstLoadedItemList && dashboardItem.error != null + + with(dashboardItemLoadedList) { + removeAll { it.type == dashboardItem.type && !isForceRefreshError && !isFirstRunDataLoadedError } + if (!isForceRefreshError && !isFirstRunDataLoadedError) add(dashboardItem) + } + + sortDashboardItems() + + if (dashboardItem is DashboardItem.AdminMessages) { + if (!dashboardItem.isDataLoaded) { + dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE + dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADMIN_MESSAGE + + dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE } + } else { + dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADMIN_MESSAGE + dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADMIN_MESSAGE + } + } + + if (dashboardItem is DashboardItem.Ads) { + if (!dashboardItem.isDataLoaded) { + dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADS + dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADS + + dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADS } + } else { + dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADS + dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADS + } + } + + if (forceRefresh) { + updateForceRefreshData(dashboardItem) + } else { + updateNormalData() + } + } + + private fun updateNormalData() { + val isItemsLoaded = + dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } } + val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all { + it.isDataLoaded || it.error != null + } + + if (isItemsDataLoaded) { + view?.run { + showProgress(false) + showErrorView(false) + showContent(true) + updateData(dashboardItemLoadedList.toList()) + } + } + + showErrorIfExists( + isItemsLoaded = isItemsLoaded, + itemsLoadedList = dashboardItemLoadedList, + forceRefresh = false + ) + } + + private fun updateForceRefreshData(dashboardItem: DashboardItem) { + val isNotLoadedAdminMessage = + dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded + + with(dashboardItemRefreshLoadedList) { + removeAll { it.type == dashboardItem.type } + if (!isNotLoadedAdminMessage) add(dashboardItem) + } + + val isRefreshItemLoaded = + dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } } + val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all { + it.isDataLoaded || it.error != null + } + + if (isRefreshItemsDataLoaded) { + view?.run { + showRefresh(false) + showErrorView(false) + showContent(true) + updateData(dashboardItemLoadedList.toList()) + } + } + + showErrorIfExists( + isItemsLoaded = isRefreshItemLoaded, + itemsLoadedList = dashboardItemRefreshLoadedList, + forceRefresh = true + ) + + if (isRefreshItemsDataLoaded) dashboardItemRefreshLoadedList.clear() + } + + private fun showErrorIfExists( + isItemsLoaded: Boolean, + itemsLoadedList: List, + forceRefresh: Boolean + ) { + val filteredItems = itemsLoadedList.filterNot { + it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE + } + val dataLoadedAdminMessageItem = + itemsLoadedList.find { it.type == DashboardItem.Type.ADMIN_MESSAGE && it.isDataLoaded } as DashboardItem.AdminMessages? + val isAccountItemError = + itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null + val isGeneralError = + filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError + val firstError = itemsLoadedList.firstNotNullOfOrNull { it.error } + + val filteredOriginalLoadedList = + dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT } + val wasAccountItemError = + dashboardItemLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null + val wasGeneralError = + filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError + + if (isGeneralError && isItemsLoaded) { + lastError = requireNotNull(firstError) + + view?.run { + showProgress(false) + showRefresh(false) + if ((forceRefresh && wasGeneralError) || !forceRefresh) { + showContent(false) + showErrorView(true, dataLoadedAdminMessageItem) + setErrorDetails(lastError) + } + } + } + } + + private fun sortDashboardItems() { + val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition + + dashboardItemLoadedList.sortBy { tile -> + val defaultPosition = if (tile is DashboardItem.AdminMessages) { + -1 + } else { + tile.type.ordinal + 100 + } + + dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal + } + } + + private fun Flow>.launchWithUniqueRefreshJob(name: String, forceRefresh: Boolean) { + val jobName = if (forceRefresh) "$name-forceRefresh" else name + + if (forceRefresh) { + onEach { + if (it is Resource.Success) { + cancelJobs(jobName) + } else if (it is Resource.Error) { + cancelJobs(jobName) + } + }.launch(jobName) + } else { + launch(jobName) + } + } + + @JvmName("launchWithUniqueRefreshJobHorizontalGroup") + private fun Flow>, *>>.launchWithUniqueRefreshJob( + name: String, + forceRefresh: Boolean + ) { + val jobName = if (forceRefresh) "$name-forceRefresh" else name + + if (forceRefresh) { + onEach { (resources, _) -> + if (resources.all { it is Resource.Success<*> }) { + cancelJobs(jobName) + } else if (resources.any { it is Resource.Error<*> }) { + cancelJobs(jobName) + } + }.launch(jobName) + } else { + launch(jobName) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt new file mode 100644 index 00000000..56a0a773 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.ui.modules.dashboard + +import io.github.wulkanowy.ui.base.BaseView + +interface DashboardView : BaseView { + + val tileWidth: Int + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun showDashboardTileSettings(selectedItems: List) + + fun showProgress(show: Boolean) + + fun showContent(show: Boolean) + + fun showRefresh(show: Boolean) + + fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages? = null) + + fun setErrorDetails(error: Throwable) + + fun resetView() + + fun popViewToRoot() + + fun openNotificationsCenterView() + + fun openInternetBrowser(url: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt new file mode 100644 index 00000000..7c74cae8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt @@ -0,0 +1,819 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMarginsRelative +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableHeader +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.databinding.* +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder +import io.github.wulkanowy.utils.* +import timber.log.Timber +import java.time.Duration +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* +import javax.inject.Inject +import kotlin.concurrent.timer + +class DashboardAdapter @Inject constructor() : RecyclerView.Adapter() { + + private var lessonsTimer: Timer? = null + + var onAccountTileClickListener: (Student) -> Unit = {} + + var onLuckyNumberTileClickListener: () -> Unit = {} + + var onMessageTileClickListener: () -> Unit = {} + + var onGradeTileClickListener: () -> Unit = {} + + var onAttendanceTileClickListener: () -> Unit = {} + + var onLessonsTileClickListener: (LocalDate) -> Unit = {} + + var onHomeworkTileClickListener: () -> Unit = {} + + var onAnnouncementsTileClickListener: () -> Unit = {} + + var onExamsTileClickListener: () -> Unit = {} + + var onConferencesTileClickListener: () -> Unit = {} + + var onAdminMessageClickListener: (String?) -> Unit = {} + + var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {} + + val items = mutableListOf() + + fun submitList(newItems: List) { + val diffResult = + DiffUtil.calculateDiff(DiffCallback(newItems, items.toMutableList())) + + with(items) { + clear() + addAll(newItems) + } + + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].type.ordinal + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder( + ItemDashboardAccountBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder( + ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.GRADES.ordinal -> GradesViewHolder( + ItemDashboardGradesBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder( + ItemDashboardLessonsBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder( + ItemDashboardHomeworkBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder( + ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder( + ItemDashboardExamsBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder( + ItemDashboardConferencesBinding.inflate(inflater, parent, false) + ) + DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder( + ItemDashboardAdminMessageBinding.inflate(inflater, parent, false), + onAdminMessageDismissClickListener = onAdminMessageDismissClickListener, + onAdminMessageClickListener = onAdminMessageClickListener, + ) + DashboardItem.Type.ADS.ordinal -> AdsViewHolder( + ItemDashboardAdsBinding.inflate(inflater, parent, false) + ) + else -> throw IllegalArgumentException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is AccountViewHolder -> bindAccountViewHolder(holder, position) + is HorizontalGroupViewHolder -> bindHorizontalGroupViewHolder(holder, position) + is GradesViewHolder -> bindGradesViewHolder(holder, position) + is LessonsViewHolder -> bindLessonsViewHolder(holder, position) + is HomeworkViewHolder -> bindHomeworkViewHolder(holder, position) + is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position) + is ExamsViewHolder -> bindExamsViewHolder(holder, position) + is ConferencesViewHolder -> bindConferencesViewHolder(holder, position) + is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage) + is AdsViewHolder -> bindAdsViewHolder(holder, position) + } + } + + fun clearTimers() { + lessonsTimer?.let { + it.cancel() + it.purge() + } + lessonsTimer = null + } + + private fun bindAccountViewHolder(accountViewHolder: AccountViewHolder, position: Int) { + val item = items[position] as DashboardItem.Account + val student = item.student + val isLoading = item.isLoading + + val avatar = student?.let { + accountViewHolder.binding.root.context.createNameInitialsDrawable( + text = it.nickOrName, + backgroundColor = it.avatarColor + ) + } + + with(accountViewHolder.binding) { + dashboardAccountItemContent.isVisible = !isLoading + dashboardAccountItemProgress.isVisible = isLoading + + dashboardAccountItemAvatar.setImageDrawable(avatar) + dashboardAccountItemName.text = student?.nickOrName.orEmpty() + dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty() + + root.setOnClickListener { student?.let(onAccountTileClickListener) } + } + } + + @SuppressLint("SetTextI18n") + private fun bindHorizontalGroupViewHolder( + horizontalGroupViewHolder: HorizontalGroupViewHolder, + position: Int + ) { + val item = items[position] as DashboardItem.HorizontalGroup + val isLoadingVisible = + (item.isLoading && !item.isDataLoaded) || (item.isLoading && !item.isFullDataLoaded) + val isWideErrorShow = isLoadingVisible || item.error != null + + with(horizontalGroupViewHolder.binding) { + dashboardHorizontalGroupItemInfoContainer.isVisible = isWideErrorShow + dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible + dashboardHorizontalGroupItemInfoErrorText.isVisible = item.error != null + + bindLuckyNumber(item, isWideErrorShow) + bindMessages(item, isWideErrorShow) + bindAttendance(item, isWideErrorShow) + } + } + + private fun ItemDashboardHorizontalGroupBinding.bindLuckyNumber( + item: DashboardItem.HorizontalGroup, + isWideErrorShow: Boolean + ) { + with(dashboardHorizontalGroupItemLuckyValue) { + isVisible = item.luckyNumber?.error != true + text = if (item.luckyNumber?.data == 0) { + context.getString(R.string.dashboard_horizontal_group_no_data) + } else item.luckyNumber?.data?.toString() + } + dashboardHorizontalGroupItemLuckyError.isVisible = item.luckyNumber?.error == true + with(dashboardHorizontalGroupItemLuckyContainer) { + isVisible = item.luckyNumber?.isHidden == false && !isWideErrorShow + setOnClickListener { onLuckyNumberTileClickListener() } + + val isAttendanceHidden = item.attendancePercentage?.isHidden == true + val isMessagesHidden = item.unreadMessagesCount?.isHidden == true + val isLuckyNumberHidden = item.luckyNumber?.isHidden == true + + updateLayoutParams { + updateMarginsRelative( + end = if (isAttendanceHidden && isMessagesHidden && !isLuckyNumberHidden) { + 0 + } else context.dpToPx(8f).toInt() + ) + } + } + } + + private fun ItemDashboardHorizontalGroupBinding.bindMessages( + item: DashboardItem.HorizontalGroup, + isWideErrorShow: Boolean + ) { + dashboardHorizontalGroupItemMessageError.isVisible = item.unreadMessagesCount?.error == true + with(dashboardHorizontalGroupItemMessageValue) { + isVisible = item.unreadMessagesCount?.error != true + text = item.unreadMessagesCount?.data.toString() + } + with(dashboardHorizontalGroupItemMessageContainer) { + isVisible = item.unreadMessagesCount?.isHidden == false && !isWideErrorShow + setOnClickListener { onMessageTileClickListener() } + } + } + + private fun ItemDashboardHorizontalGroupBinding.bindAttendance( + item: DashboardItem.HorizontalGroup, + isWideErrorShow: Boolean + ) { + val attendancePercentage = item.attendancePercentage?.data + val attendanceColor = when { + attendancePercentage == null || attendancePercentage == .0 -> { + root.context.getThemeAttrColor(R.attr.colorOnSurface) + } + attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> { + root.context.getThemeAttrColor(R.attr.colorPrimary) + } + attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> { + root.context.getThemeAttrColor(R.attr.colorTimetableChange) + } + else -> root.context.getThemeAttrColor(R.attr.colorOnSurface) + } + val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) { + root.context.getString(R.string.dashboard_horizontal_group_no_data) + } else { + "%.2f%%".format(attendancePercentage) + } + + dashboardHorizontalGroupItemAttendanceError.isVisible = + item.attendancePercentage?.error == true + with(dashboardHorizontalGroupItemAttendanceValue) { + isVisible = item.attendancePercentage?.error != true + text = attendanceString + setTextColor(attendanceColor) + } + with(dashboardHorizontalGroupItemAttendanceContainer) { + isVisible = item.attendancePercentage?.isHidden == false && !isWideErrorShow + setOnClickListener { onAttendanceTileClickListener() } + updateLayoutParams { + matchConstraintPercentWidth = when { + item.luckyNumber?.isHidden == true && item.unreadMessagesCount?.isHidden == true -> 1.0f + item.luckyNumber?.isHidden == true || item.unreadMessagesCount?.isHidden == true -> 0.5f + else -> 0.4f + } + } + } + } + + private fun bindGradesViewHolder(gradesViewHolder: GradesViewHolder, position: Int) { + val item = items[position] as DashboardItem.Grades + val subjectWithGrades = item.subjectWithGrades.orEmpty() + val gradeTheme = item.gradeTheme + val error = item.error + val isLoading = item.isLoading + val dashboardGradesAdapter = gradesViewHolder.adapter.apply { + this.items = subjectWithGrades.toList() + this.gradeColorTheme = gradeTheme ?: GradeColorTheme.VULCAN + } + + with(gradesViewHolder.binding) { + dashboardGradesItemEmpty.isVisible = + subjectWithGrades.isEmpty() && error == null && !isLoading + dashboardGradesItemError.isVisible = error != null && !isLoading + dashboardGradesItemProgress.isVisible = + isLoading && error == null && subjectWithGrades.isEmpty() + + with(dashboardGradesItemRecycler) { + adapter = dashboardGradesAdapter + layoutManager = LinearLayoutManager(context) + isVisible = subjectWithGrades.isNotEmpty() && error == null + suppressLayout(true) + } + + root.setOnClickListener { onGradeTileClickListener() } + } + } + + private fun bindLessonsViewHolder(lessonsViewHolder: LessonsViewHolder, position: Int) { + val item = items[position] as DashboardItem.Lessons + val timetableFull = item.lessons + val binding = lessonsViewHolder.binding + var dateToNavigate = LocalDate.now() + + fun updateLessonState() { + val currentDateTime = Instant.now() + val currentDate = LocalDate.now() + val tomorrowDate = currentDate.plusDays(1) + + val currentTimetable = timetableFull?.lessons + .orEmpty() + .filter { it.date == currentDate } + .filter { it.end.isAfter(currentDateTime) } + .filterNot { it.canceled } + val currentDayHeader = + timetableFull?.headers.orEmpty().singleOrNull { it.date == currentDate } + + val tomorrowTimetable = timetableFull?.lessons + .orEmpty() + .filter { it.date == currentDate.plusDays(1) } + .filterNot { it.canceled } + val tomorrowDayHeader = + timetableFull?.headers.orEmpty().singleOrNull { it.date == currentDate.plusDays(1) } + + when { + currentTimetable.isNotEmpty() -> { + dateToNavigate = currentDate + updateLessonView(item, currentTimetable, binding) + binding.dashboardLessonsItemTitleTomorrow.isVisible = false + binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false + } + tomorrowTimetable.isNotEmpty() -> { + dateToNavigate = tomorrowDate + updateLessonView(item, tomorrowTimetable, binding) + binding.dashboardLessonsItemTitleTomorrow.isVisible = true + binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false + } + currentDayHeader != null && currentDayHeader.content.isNotBlank() -> { + dateToNavigate = currentDate + updateLessonView(item, emptyList(), binding, currentDayHeader) + binding.dashboardLessonsItemTitleTomorrow.isVisible = false + binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false + } + tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> { + dateToNavigate = tomorrowDate + updateLessonView(item, emptyList(), binding, tomorrowDayHeader) + binding.dashboardLessonsItemTitleTomorrow.isVisible = true + binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false + } + else -> { + dateToNavigate = currentDate + updateLessonView(item, emptyList(), binding) + binding.dashboardLessonsItemTitleTomorrow.isVisible = false + binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = + !(item.isLoading && item.error == null) + } + } + } + + updateLessonState() + + lessonsTimer?.cancel() + lessonsTimer = timer(period = 1000) { + Handler(Looper.getMainLooper()).post { updateLessonState() } + } + + binding.root.setOnClickListener { onLessonsTileClickListener(dateToNavigate) } + } + + private fun updateLessonView( + item: DashboardItem.Lessons, + timetableToShow: List, + binding: ItemDashboardLessonsBinding, + header: TimetableHeader? = null, + ) { + val currentDateTime = Instant.now() + val nextLessons = timetableToShow.filter { it.end.isAfter(currentDateTime) } + .sortedBy { it.start } + + with(binding) { + dashboardLessonsItemEmpty.isVisible = + (timetableToShow.isEmpty() || nextLessons.isEmpty()) && item.error == null && header == null && !item.isLoading + dashboardLessonsItemError.isVisible = item.error != null && !item.isLoading + dashboardLessonsItemProgress.isVisible = + item.isLoading && (timetableToShow.isEmpty() || nextLessons.isEmpty()) && item.error == null && header == null + + val secondLesson = nextLessons.getOrNull(1) + val firstLesson = nextLessons.getOrNull(0) + + updateFirstLessonView(binding, firstLesson, currentDateTime) + updateSecondLesson(binding, firstLesson, secondLesson) + updateLessonSummary(binding, nextLessons) + updateLessonHeader(binding, header) + } + } + + @SuppressLint("SetTextI18n") + private fun updateFirstLessonView( + binding: ItemDashboardLessonsBinding, + firstLesson: Timetable?, + currentDateTime: Instant + ) { + val context = binding.root.context + val sansSerifFont = Typeface.create("sans-serif", Typeface.NORMAL) + val sansSerifMediumFont = Typeface.create("sans-serif-medium", Typeface.NORMAL) + + with(binding) { + dashboardLessonsItemFirstTitle.isVisible = firstLesson != null + dashboardLessonsItemFirstTime.isVisible = firstLesson != null + dashboardLessonsItemFirstTimeRange.isVisible = firstLesson != null + dashboardLessonsItemFirstValue.isVisible = firstLesson != null + } + + firstLesson ?: return + + val minutesToStartLesson = + Duration.between(currentDateTime, firstLesson.start).toMinutes() + 1 + val isFirstTimeVisible: Boolean + val isFirstTimeRangeVisible: Boolean + val firstTimeText: String + val firstTimeRangeText: String + val firstTitleText: String + val firstTitleAndValueTextColor: Int + val firstTitleAndValueTextFont: Typeface + + if (currentDateTime < firstLesson.start) { + if (minutesToStartLesson > 60) { + val formattedStartTime = firstLesson.start.toFormattedString("HH:mm") + val formattedEndTime = firstLesson.end.toFormattedString("HH:mm") + + firstTimeRangeText = "$formattedStartTime - $formattedEndTime" + firstTimeText = "" + + isFirstTimeRangeVisible = true + isFirstTimeVisible = false + } else { + firstTimeText = context.getString( + R.string.timetable_time_until, + context.getString( + R.string.timetable_minutes, + minutesToStartLesson.toString() + ) + ) + firstTimeRangeText = "" + + isFirstTimeRangeVisible = false + isFirstTimeVisible = true + } + + when { + minutesToStartLesson < 60 -> { + firstTitleAndValueTextColor = context.getThemeAttrColor(R.attr.colorPrimary) + firstTitleAndValueTextFont = sansSerifMediumFont + firstTitleText = + context.getString(R.string.dashboard_timetable_first_lesson_title_moment) + } + minutesToStartLesson < 240 -> { + firstTitleAndValueTextColor = + context.getThemeAttrColor(R.attr.colorOnSurface) + firstTitleAndValueTextFont = sansSerifFont + firstTitleText = + context.getString(R.string.dashboard_timetable_first_lesson_title_soon) + } + else -> { + firstTitleAndValueTextColor = + context.getThemeAttrColor(R.attr.colorOnSurface) + firstTitleAndValueTextFont = sansSerifFont + firstTitleText = + context.getString(R.string.dashboard_timetable_first_lesson_title_first) + } + } + } else { + val minutesToEndLesson = firstLesson.left?.toMinutes()?.plus(1) ?: run { + Timber.e(IllegalArgumentException("Lesson left is null. START ${firstLesson.start} ; END ${firstLesson.end} ; CURRENT ${LocalDateTime.now()}")) + 0 + } + + firstTimeText = context.getString( + R.string.timetable_time_left, + context.getString( + R.string.timetable_minutes, + minutesToEndLesson.toString() + ) + ) + firstTimeRangeText = "" + + isFirstTimeRangeVisible = false + isFirstTimeVisible = true + + firstTitleAndValueTextColor = context.getThemeAttrColor(R.attr.colorPrimary) + firstTitleAndValueTextFont = sansSerifMediumFont + firstTitleText = context.getString(R.string.dashboard_timetable_first_lesson_title_now) + } + + with(binding.dashboardLessonsItemFirstTime) { + isVisible = isFirstTimeVisible + text = firstTimeText + } + with(binding.dashboardLessonsItemFirstTimeRange) { + isVisible = isFirstTimeRangeVisible + text = firstTimeRangeText + } + with(binding.dashboardLessonsItemFirstTitle) { + setTextColor(firstTitleAndValueTextColor) + typeface = firstTitleAndValueTextFont + text = firstTitleText + } + with(binding.dashboardLessonsItemFirstValue) { + setTextColor(firstTitleAndValueTextColor) + typeface = firstTitleAndValueTextFont + text = + "${firstLesson.subject} ${if (firstLesson.room.isNotBlank()) "(${firstLesson.room})" else ""}" + } + } + + private fun updateSecondLesson( + binding: ItemDashboardLessonsBinding, + firstLesson: Timetable?, + secondLesson: Timetable? + ) { + val context = binding.root.context + + val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm") + val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm") + + val secondTimeText = "$formattedStartTime - $formattedEndTime" + val secondValueText = if (secondLesson != null) { + val roomString = if (secondLesson.room.isNotBlank()) "(${secondLesson.room})" else "" + + "${secondLesson.subject} $roomString" + } else { + context.getString(R.string.dashboard_timetable_second_lesson_value_end) + } + + with(binding.dashboardLessonsItemSecondTime) { + isVisible = secondLesson != null + text = secondTimeText + } + with(binding.dashboardLessonsItemSecondValue) { + isVisible = !(secondLesson == null && firstLesson == null) + text = secondValueText + } + binding.dashboardLessonsItemSecondTitle.isVisible = + !(secondLesson == null && firstLesson == null) + } + + private fun updateLessonSummary( + binding: ItemDashboardLessonsBinding, + nextLessons: List + ) { + val context = binding.root.context + val formattedEndTime = nextLessons.lastOrNull()?.end?.toFormattedString("HH:mm") + + with(binding) { + dashboardLessonsItemThirdTime.isVisible = + nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD + dashboardLessonsItemThirdTitle.isVisible = + nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD + dashboardLessonsItemThirdValue.isVisible = + nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD + dashboardLessonsItemDivider.isVisible = + nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD + + dashboardLessonsItemThirdValue.text = context.resources.getQuantityString( + R.plurals.dashboard_timetable_third_value, + nextLessons.size - LESSON_SUMMARY_VISIBILITY_THRESHOLD, + nextLessons.size - LESSON_SUMMARY_VISIBILITY_THRESHOLD + ) + dashboardLessonsItemThirdTime.text = + context.getString(R.string.dashboard_timetable_third_time, formattedEndTime) + } + } + + private fun updateLessonHeader( + binding: ItemDashboardLessonsBinding, + header: TimetableHeader? + ) { + with(binding.dashboardLessonsItemDayHeader) { + isVisible = header != null + text = header?.content?.parseAsHtml() + } + } + + private fun bindHomeworkViewHolder(homeworkViewHolder: HomeworkViewHolder, position: Int) { + val item = items[position] as DashboardItem.Homework + val homeworkList = item.homework.orEmpty() + val error = item.error + val isLoading = item.isLoading + val context = homeworkViewHolder.binding.root.context + val homeworkAdapter = homeworkViewHolder.adapter.apply { + this.items = homeworkList.take(MAX_VISIBLE_LIST_ITEMS) + } + + with(homeworkViewHolder.binding) { + dashboardHomeworkItemEmpty.isVisible = + homeworkList.isEmpty() && error == null && !isLoading + dashboardHomeworkItemError.isVisible = error != null && !isLoading + dashboardHomeworkItemProgress.isVisible = + isLoading && error == null && homeworkList.isEmpty() + dashboardHomeworkItemDivider.isVisible = homeworkList.size > MAX_VISIBLE_LIST_ITEMS + dashboardHomeworkItemMore.isVisible = homeworkList.size > MAX_VISIBLE_LIST_ITEMS + dashboardHomeworkItemMore.text = context.resources.getQuantityString( + R.plurals.dashboard_homework_more, + homeworkList.size - MAX_VISIBLE_LIST_ITEMS, + homeworkList.size - MAX_VISIBLE_LIST_ITEMS + ) + + with(dashboardHomeworkItemRecycler) { + adapter = homeworkAdapter + layoutManager = LinearLayoutManager(context) + isVisible = homeworkList.isNotEmpty() && error == null + suppressLayout(true) + } + + root.setOnClickListener { onHomeworkTileClickListener() } + } + } + + private fun bindAnnouncementsViewHolder( + announcementsViewHolder: AnnouncementsViewHolder, + position: Int + ) { + val item = items[position] as DashboardItem.Announcements + val schoolAnnouncementList = item.announcement.orEmpty() + val error = item.error + val isLoading = item.isLoading + val context = announcementsViewHolder.binding.root.context + val schoolAnnouncementsAdapter = announcementsViewHolder.adapter.apply { + this.items = schoolAnnouncementList.take(MAX_VISIBLE_LIST_ITEMS) + } + + with(announcementsViewHolder.binding) { + dashboardAnnouncementsItemEmpty.isVisible = + schoolAnnouncementList.isEmpty() && error == null && !isLoading + dashboardAnnouncementsItemError.isVisible = error != null && !isLoading + dashboardAnnouncementsItemProgress.isVisible = + isLoading && error == null && schoolAnnouncementList.isEmpty() + dashboardAnnouncementsItemDivider.isVisible = + schoolAnnouncementList.size > MAX_VISIBLE_LIST_ITEMS + dashboardAnnouncementsItemMore.isVisible = + schoolAnnouncementList.size > MAX_VISIBLE_LIST_ITEMS + dashboardAnnouncementsItemMore.text = context.resources.getQuantityString( + R.plurals.dashboard_announcements_more, + schoolAnnouncementList.size - MAX_VISIBLE_LIST_ITEMS, + schoolAnnouncementList.size - MAX_VISIBLE_LIST_ITEMS + ) + + with(dashboardAnnouncementsItemRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = schoolAnnouncementsAdapter + isVisible = schoolAnnouncementList.isNotEmpty() && error == null + suppressLayout(true) + } + + root.setOnClickListener { onAnnouncementsTileClickListener() } + } + } + + private fun bindExamsViewHolder(examsViewHolder: ExamsViewHolder, position: Int) { + val item = items[position] as DashboardItem.Exams + val exams = item.exams.orEmpty() + val error = item.error + val isLoading = item.isLoading + val context = examsViewHolder.binding.root.context + val examAdapter = examsViewHolder.adapter.apply { + this.items = exams.take(MAX_VISIBLE_LIST_ITEMS) + } + + with(examsViewHolder.binding) { + dashboardExamsItemEmpty.isVisible = exams.isEmpty() && error == null && !isLoading + dashboardExamsItemError.isVisible = error != null && !isLoading + dashboardExamsItemProgress.isVisible = isLoading && error == null && exams.isEmpty() + dashboardExamsItemDivider.isVisible = exams.size > MAX_VISIBLE_LIST_ITEMS + dashboardExamsItemMore.isVisible = exams.size > MAX_VISIBLE_LIST_ITEMS + dashboardExamsItemMore.text = context.resources.getQuantityString( + R.plurals.dashboard_exams_more, + exams.size - MAX_VISIBLE_LIST_ITEMS, + exams.size - MAX_VISIBLE_LIST_ITEMS + ) + + with(dashboardExamsItemRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = examAdapter + isVisible = exams.isNotEmpty() && error == null + suppressLayout(true) + } + + root.setOnClickListener { onExamsTileClickListener() } + } + } + + private fun bindConferencesViewHolder( + conferencesViewHolder: ConferencesViewHolder, + position: Int + ) { + val item = items[position] as DashboardItem.Conferences + val conferences = item.conferences.orEmpty() + val error = item.error + val isLoading = item.isLoading + val context = conferencesViewHolder.binding.root.context + val conferenceAdapter = conferencesViewHolder.adapter.apply { + this.items = conferences.take(MAX_VISIBLE_LIST_ITEMS) + } + + with(conferencesViewHolder.binding) { + dashboardConferencesItemEmpty.isVisible = + conferences.isEmpty() && error == null && !isLoading + dashboardConferencesItemError.isVisible = error != null && !isLoading + dashboardConferencesItemProgress.isVisible = + isLoading && error == null && conferences.isEmpty() + dashboardConferencesItemDivider.isVisible = conferences.size > MAX_VISIBLE_LIST_ITEMS + dashboardConferencesItemMore.isVisible = conferences.size > MAX_VISIBLE_LIST_ITEMS + dashboardConferencesItemMore.text = context.resources.getQuantityString( + R.plurals.dashboard_conference_more, + conferences.size - MAX_VISIBLE_LIST_ITEMS, + conferences.size - MAX_VISIBLE_LIST_ITEMS + ) + + with(dashboardConferencesItemRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = conferenceAdapter + isVisible = conferences.isNotEmpty() && error == null + suppressLayout(true) + } + + root.setOnClickListener { onConferencesTileClickListener() } + } + } + + private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) { + val item = (items[position] as DashboardItem.Ads).adBanner ?: return + val binding = adsViewHolder.binding + + binding.dashboardAdminMessageItemContent.removeAllViews() + binding.dashboardAdminMessageItemContent.addView( + item.view, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ) + } + + class AccountViewHolder(val binding: ItemDashboardAccountBinding) : + RecyclerView.ViewHolder(binding.root) + + class HorizontalGroupViewHolder(val binding: ItemDashboardHorizontalGroupBinding) : + RecyclerView.ViewHolder(binding.root) + + class GradesViewHolder(val binding: ItemDashboardGradesBinding) : + RecyclerView.ViewHolder(binding.root) { + + val adapter by lazy { DashboardGradesAdapter() } + } + + class LessonsViewHolder(val binding: ItemDashboardLessonsBinding) : + RecyclerView.ViewHolder(binding.root) + + class HomeworkViewHolder(val binding: ItemDashboardHomeworkBinding) : + RecyclerView.ViewHolder(binding.root) { + + val adapter by lazy { DashboardHomeworkAdapter() } + } + + class AnnouncementsViewHolder(val binding: ItemDashboardAnnouncementsBinding) : + RecyclerView.ViewHolder(binding.root) { + + val adapter by lazy { DashboardAnnouncementsAdapter() } + } + + class ExamsViewHolder(val binding: ItemDashboardExamsBinding) : + RecyclerView.ViewHolder(binding.root) { + + val adapter by lazy { DashboardExamsAdapter() } + } + + class ConferencesViewHolder(val binding: ItemDashboardConferencesBinding) : + RecyclerView.ViewHolder(binding.root) { + + val adapter by lazy { DashboardConferencesAdapter() } + } + + class AdsViewHolder(val binding: ItemDashboardAdsBinding) : + RecyclerView.ViewHolder(binding.root) + + private class DiffCallback( + private val newList: List, + private val oldList: List + ) : DiffUtil.Callback() { + + override fun getNewListSize() = newList.size + + override fun getOldListSize() = oldList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + newList[newItemPosition] == oldList[oldItemPosition] + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + newList[newItemPosition].type == oldList[oldItemPosition].type + } + + private companion object { + + private const val LESSON_SUMMARY_VISIBILITY_THRESHOLD = 2 + + private const val MAX_VISIBLE_LIST_ITEMS = 5 + + private const val ATTENDANCE_FIRST_WARNING_THRESHOLD = 75.0 + + private const val ATTENDANCE_SECOND_WARNING_THRESHOLD = 50.0 + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt new file mode 100644 index 00000000..da588015 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.databinding.SubitemDashboardAnnouncementsBinding +import io.github.wulkanowy.utils.toFormattedString + +class DashboardAnnouncementsAdapter : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + SubitemDashboardAnnouncementsBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + dashboardHomeworkSubitemTime.text = item.date.toFormattedString() + dashboardHomeworkSubitemTitle.text = item.subject + } + } + + class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt new file mode 100644 index 00000000..1244ff60 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.SubitemDashboardConferencesBinding +import io.github.wulkanowy.utils.toFormattedString + +class DashboardConferencesAdapter : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + SubitemDashboardConferencesBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + dashboardHomeworkSubitemTime.text = item.date.toFormattedString("HH:mm dd.MM.yyyy") + dashboardHomeworkSubitemTitle.text = item.title + } + } + + class ViewHolder(val binding: SubitemDashboardConferencesBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt new file mode 100644 index 00000000..583bf29d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt @@ -0,0 +1,59 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.SubitemDashboardExamsBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate + +class DashboardExamsAdapter : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + SubitemDashboardExamsBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + val context = holder.binding.root.context + val primaryWarningTextColor = context.getThemeAttrColor( + if (item.date == LocalDate.now()) { + R.attr.colorPrimary + } else { + android.R.attr.textColorPrimary + } + ) + val secondaryWarningTextColor = context.getThemeAttrColor( + if (item.date == LocalDate.now()) { + R.attr.colorPrimary + } else { + android.R.attr.textColorSecondary + } + ) + + with(holder.binding) { + dashboardHomeworkSubitemTime.text = item.date.toFormattedString("dd.MM") + dashboardHomeworkSubitemTime.setTextColor(secondaryWarningTextColor) + + dashboardHomeworkSubitemTitle.text = "${item.type} - ${item.subject}" + dashboardHomeworkSubitemTitle.setTextColor(primaryWarningTextColor) + } + } + + class ViewHolder(val binding: SubitemDashboardExamsBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt new file mode 100644 index 00000000..d821de53 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.content.res.ColorStateList +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding +import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding +import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor + +class DashboardGradesAdapter : RecyclerView.Adapter() { + + var items = listOf>>() + + lateinit var gradeColorTheme: GradeColorTheme + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + SubitemDashboardGradesBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val (subject, grades) = items[position] + val context = holder.binding.root.context + + with(holder.binding) { + dashboardGradesSubitemTitle.text = subject + + grades.forEach { + val subitemBinding = SubitemDashboardSmallGradeBinding.inflate( + LayoutInflater.from(context), + dashboardGradesSubitemGradeContainer, + false + ) + + with(subitemBinding.dashboardSmallGradeSubitemValue) { + text = it.entry + backgroundTintList = ColorStateList.valueOf( + context.getCompatColor(it.getBackgroundColor(gradeColorTheme)) + ) + } + + dashboardGradesSubitemGradeContainer.addView(subitemBinding.root) + } + } + } + + class ViewHolder(val binding: SubitemDashboardGradesBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt new file mode 100644 index 00000000..8105ff9c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt @@ -0,0 +1,56 @@ +package io.github.wulkanowy.ui.modules.dashboard.adapters + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.SubitemDashboardHomeworkBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate + +class DashboardHomeworkAdapter : RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + SubitemDashboardHomeworkBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + val context = holder.binding.root.context + val formattedDate = item.date.toFormattedString("dd.MM") + val primaryWarningTextColor = context.getThemeAttrColor( + if (item.date == LocalDate.now()) { + R.attr.colorPrimary + } else { + android.R.attr.textColorPrimary + } + ) + val secondaryWarningTextColor = context.getThemeAttrColor( + if (item.date == LocalDate.now()) { + R.attr.colorPrimary + } else { + android.R.attr.textColorSecondary + } + ) + + with(holder.binding) { + dashboardHomeworkSubitemTitle.text = "${item.subject} - ${item.content}" + dashboardHomeworkSubitemTitle.setTextColor(primaryWarningTextColor) + + dashboardHomeworkSubitemTime.text = + context.getString(R.string.dashboard_homework_time, formattedDate) + dashboardHomeworkSubitemTime.setTextColor(secondaryWarningTextColor) + } + } + + class ViewHolder(val binding: SubitemDashboardHomeworkBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt new file mode 100644 index 00000000..1e0f0bdb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.modules.dashboard.viewholders + +import android.content.res.ColorStateList +import android.graphics.Color +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding +import io.github.wulkanowy.utils.getThemeAttrColor + +class AdminMessageViewHolder( + private val binding: ItemDashboardAdminMessageBinding, + private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit, + private val onAdminMessageClickListener: (String?) -> Unit, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: AdminMessage?) { + item ?: return + + val context = binding.root.context + val (backgroundColor, textColor) = when (item.priority) { + "HIGH" -> { + context.getThemeAttrColor(R.attr.colorMessageHigh) to + context.getThemeAttrColor(R.attr.colorOnMessageHigh) + } + + "MEDIUM" -> { + context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK + } + + else -> null to context.getThemeAttrColor(R.attr.colorOnSurface) + } + + with(binding) { + dashboardAdminMessageItemTitle.text = item.title + dashboardAdminMessageItemTitle.setTextColor(textColor) + dashboardAdminMessageItemDescription.text = item.content + dashboardAdminMessageItemDescription.setTextColor(textColor) + dashboardAdminMessageItemIcon.setColorFilter(textColor) + dashboardAdminMessageItemDismiss.isVisible = item.isOkVisible + dashboardAdminMessageItemClose.isVisible = item.isXVisible + dashboardAdminMessageItemDismiss.setTextColor(textColor) + dashboardAdminMessageItemClose.imageTintList = ColorStateList.valueOf(textColor) + dashboardAdminMessageItemDismiss.setOnClickListener { + onAdminMessageDismissClickListener(item) + } + dashboardAdminMessageItemClose.setOnClickListener { + onAdminMessageDismissClickListener(item) + } + + root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) + item.destinationUrl?.let { url -> + root.setOnClickListener { onAdminMessageClickListener(url) } + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugAdapter.kt new file mode 100644 index 00000000..36f99ca3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugAdapter.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.modules.debug + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemDebugBinding + +class DebugAdapter : RecyclerView.Adapter() { + + var items = emptyList() + + var onItemClickListener: (DebugItem) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemDebugBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + with(holder.binding) { + debugItemName.setText(item.title) + + root.setOnClickListener { + onItemClickListener(item) + } + } + } + + class ItemViewHolder(val binding: ItemDebugBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt new file mode 100644 index 00000000..9db01a30 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt @@ -0,0 +1,70 @@ +package io.github.wulkanowy.ui.modules.debug + +import android.os.Bundle +import android.view.View +import android.webkit.CookieManager +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentDebugBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.debug.logviewer.LogViewerFragment +import io.github.wulkanowy.ui.modules.debug.notification.NotificationDebugFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import javax.inject.Inject + +@AndroidEntryPoint +class DebugFragment : BaseFragment(R.layout.fragment_debug), DebugView, + MainView.TitledView { + + @Inject + lateinit var presenter: DebugPresenter + + private val debugAdapter = DebugAdapter() + + override val titleStringId: Int + get() = R.string.debug_title + + companion object { + fun newInstance() = DebugFragment() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentDebugBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + debugAdapter.onItemClickListener = presenter::onItemSelect + with(binding.debugRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = debugAdapter + } + } + + override fun setItems(itemList: List) { + with(debugAdapter) { + items = itemList + notifyDataSetChanged() + } + } + + override fun openLogViewer() { + (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance()) + } + + override fun openNotificationsDebug() { + (activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance()) + } + + override fun clearWebkitCookies() { + CookieManager.getInstance().removeAllCookies(null) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugItem.kt new file mode 100644 index 00000000..2273294e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugItem.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.ui.modules.debug + +import androidx.annotation.StringRes + +data class DebugItem( + @StringRes val title: Int, +) \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt new file mode 100644 index 00000000..816b5985 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt @@ -0,0 +1,39 @@ +package io.github.wulkanowy.ui.modules.debug + +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class DebugPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, +) : BasePresenter(errorHandler, studentRepository) { + + val items = listOf( + DebugItem(R.string.logviewer_title), + DebugItem(R.string.notification_debug_title), + DebugItem(R.string.debug_cookies_clear), + ) + + override fun onAttachView(view: DebugView) { + super.onAttachView(view) + Timber.i("Debug view was initialized") + + with(view) { + initView() + setItems(items) + } + } + + fun onItemSelect(item: DebugItem) { + when (item.title) { + R.string.logviewer_title -> view?.openLogViewer() + R.string.notification_debug_title -> view?.openNotificationsDebug() + R.string.debug_cookies_clear -> view?.clearWebkitCookies() + else -> Timber.d("Unknown debug item: $item") + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt new file mode 100644 index 00000000..792d63d9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.ui.modules.debug + +import io.github.wulkanowy.ui.base.BaseView + +interface DebugView : BaseView { + + fun initView() + + fun setItems(itemList: List) + + fun openLogViewer() + + fun openNotificationsDebug() + + fun clearWebkitCookies() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerAdapter.kt similarity index 91% rename from app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerAdapter.kt index 45c304aa..5a0e2104 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.about.logviewer +package io.github.wulkanowy.ui.modules.debug.logviewer import android.view.ViewGroup import android.widget.TextView diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt similarity index 63% rename from app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt index 0b7b05c7..929e7264 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt @@ -1,30 +1,26 @@ -package io.github.wulkanowy.ui.modules.about.logviewer +package io.github.wulkanowy.ui.modules.debug.logviewer import android.content.Intent -import android.content.Intent.EXTRA_EMAIL -import android.content.Intent.EXTRA_STREAM -import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION -import android.net.Uri -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP +import android.content.Intent.* import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.core.content.FileProvider import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.BuildConfig.APPLICATION_ID import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentLogviewerBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_logviewer.* import java.io.File import javax.inject.Inject -class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { +@AndroidEntryPoint +class LogViewerFragment : BaseFragment(R.layout.fragment_logviewer), + LogViewerView, MainView.TitledView { @Inject lateinit var presenter: LogViewerPresenter @@ -38,18 +34,16 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { fun newInstance() = LogViewerFragment() } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_logviewer, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = logViewerRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLogviewerBinding.bind(view) + messageContainer = binding.logViewerRecycler presenter.onAttachView(this) } @@ -63,18 +57,18 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { } override fun initView() { - with(logViewerRecycler) { + with(binding.logViewerRecycler) { layoutManager = LinearLayoutManager(context) adapter = logAdapter } - logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() } + binding.logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() } } override fun setLines(lines: List) { logAdapter.lines = lines logAdapter.notifyDataSetChanged() - logViewerRecycler.scrollToPosition(lines.size - 1) + binding.logViewerRecycler.scrollToPosition(lines.size - 1) } override fun shareLogs(files: List) { @@ -83,8 +77,7 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView { putExtra(EXTRA_EMAIL, arrayOf("wulkanowyinc@gmail.com")) addFlags(FLAG_GRANT_READ_URI_PERMISSION) putParcelableArrayListExtra(EXTRA_STREAM, ArrayList(files.map { - if (SDK_INT < LOLLIPOP) Uri.fromFile(it) - else FileProvider.getUriForFile(requireContext(), "$APPLICATION_ID.fileprovider", it) + FileProvider.getUriForFile(requireContext(), "$APPLICATION_ID.fileprovider", it) })) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt new file mode 100644 index 00000000..7adb56b8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.ui.modules.debug.logviewer + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.repositories.LoggerRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +class LogViewerPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val loggerRepository: LoggerRepository +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: LogViewerView) { + super.onAttachView(view) + view.initView() + loadLogFile() + } + + fun onShareLogsSelected(): Boolean { + resourceFlow { loggerRepository.getLogFiles() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading logs files started") + is Resource.Success -> { + Timber.i("Loading logs files result: ${it.data.joinToString { file -> file.name }}") + view?.shareLogs(it.data) + } + is Resource.Error -> { + Timber.i("Loading logs files result: An exception occurred") + errorHandler.dispatch(it.error) + } + } + } + .launch("share") + return true + } + + fun onRefreshClick() { + loadLogFile() + } + + private fun loadLogFile() { + resourceFlow { loggerRepository.getLastLogLines() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading last log file started") + is Resource.Success -> { + Timber.i("Loading last log file result: load ${it.data.size} lines") + view?.setLines(it.data) + } + is Resource.Error -> { + Timber.i("Loading last log file result: An exception occurred") + errorHandler.dispatch(it.error) + } + } + } + .launch("file") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerView.kt similarity index 78% rename from app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerView.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerView.kt index bd98c24f..3d138ece 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerView.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.about.logviewer +package io.github.wulkanowy.ui.modules.debug.logviewer import io.github.wulkanowy.ui.base.BaseView import java.io.File diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugAdapter.kt new file mode 100644 index 00000000..78107d43 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugAdapter.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.modules.debug.notification + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemDebugNotificationsBinding + +class NotificationDebugAdapter : RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemDebugNotificationsBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + title.setText(item.title) + button1.setOnClickListener { item.onClickCallback(1) } + button2.setOnClickListener { item.onClickCallback(3) } + button3.setOnClickListener { item.onClickCallback(10) } + } + } + + class ItemViewHolder(val binding: ItemDebugNotificationsBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugFragment.kt new file mode 100644 index 00000000..c5c797d0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugFragment.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.ui.modules.debug.notification + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentDebugNotificationsBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationDebugFragment : + BaseFragment(R.layout.fragment_debug_notifications), + NotificationDebugView, MainView.TitledView { + + @Inject + lateinit var presenter: NotificationDebugPresenter + + private val notificationDebugAdapter = NotificationDebugAdapter() + + override val titleStringId: Int + get() = R.string.notification_debug_title + + companion object { + fun newInstance() = NotificationDebugFragment() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentDebugNotificationsBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.recyclerView) { + adapter = notificationDebugAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + } + + override fun setItems(notificationDebugs: List) { + with(notificationDebugAdapter) { + items = notificationDebugs + notifyDataSetChanged() + } + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugItem.kt new file mode 100644 index 00000000..5a528787 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugItem.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.ui.modules.debug.notification + +import androidx.annotation.StringRes + +data class NotificationDebugItem( + @StringRes val title: Int, + val onClickCallback: (numberOfNotifications: Int) -> Unit, +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt new file mode 100644 index 00000000..cdd186b9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt @@ -0,0 +1,116 @@ +package io.github.wulkanowy.ui.modules.debug.notification + +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification +import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification +import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification +import io.github.wulkanowy.services.sync.notifications.NewExamNotification +import io.github.wulkanowy.services.sync.notifications.NewGradeNotification +import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification +import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification +import io.github.wulkanowy.services.sync.notifications.NewMessageNotification +import io.github.wulkanowy.services.sync.notifications.NewNoteNotification +import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugLuckyNumber +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugMessageItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugNoteItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugSchoolAnnouncementItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugTimetableItems +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +class NotificationDebugPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val newGradeNotification: NewGradeNotification, + private val newHomeworkNotification: NewHomeworkNotification, + private val newConferenceNotification: NewConferenceNotification, + private val newExamNotification: NewExamNotification, + private val newMessageNotification: NewMessageNotification, + private val newNoteNotification: NewNoteNotification, + private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification, + private val newLuckyNumberNotification: NewLuckyNumberNotification, + private val changeTimetableNotification: ChangeTimetableNotification, + private val newAttendanceNotification: NewAttendanceNotification, +) : BasePresenter(errorHandler, studentRepository) { + + private val items = listOf( + NotificationDebugItem(R.string.grade_title) { n -> + withStudent { newGradeNotification.notifyDetails(debugGradeDetailsItems.take(n), it) } + }, + NotificationDebugItem(R.string.grade_summary_predicted_grade) { n -> + withStudent { newGradeNotification.notifyPredicted(debugGradeSummaryItems.take(n), it) } + }, + NotificationDebugItem(R.string.grade_summary_final_grade) { n -> + withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) } + }, + NotificationDebugItem(R.string.grade_summary_descriptive) { n -> + withStudent { + newGradeNotification.notifyDescriptive( + debugGradeDescriptiveItems.take(n), + it + ) + } + }, + NotificationDebugItem(R.string.homework_title) { n -> + withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) } + }, + NotificationDebugItem(R.string.conferences_title) { n -> + withStudent { newConferenceNotification.notify(debugConferenceItems.take(n), it) } + }, + NotificationDebugItem(R.string.exam_title) { n -> + withStudent { newExamNotification.notify(debugExamItems.take(n), it) } + }, + NotificationDebugItem(R.string.message_title) { n -> + withStudent { newMessageNotification.notify(debugMessageItems.take(n), it) } + }, + NotificationDebugItem(R.string.note_title) { n -> + withStudent { newNoteNotification.notify(debugNoteItems.take(n), it) } + }, + NotificationDebugItem(R.string.attendance_title) { n -> + withStudent { newAttendanceNotification.notify(debugAttendanceItems.take(n), it) } + }, + NotificationDebugItem(R.string.timetable_title) { n -> + withStudent { changeTimetableNotification.notify(debugTimetableItems.take(n), it) } + }, + NotificationDebugItem(R.string.school_announcement_title) { n -> + withStudent { + newSchoolAnnouncementNotification.notify(debugSchoolAnnouncementItems.take(n), it) + } + }, + NotificationDebugItem(R.string.lucky_number_title) { n -> + withStudent { + repeat(n) { _ -> + newLuckyNumberNotification.notify(debugLuckyNumber, it) + } + } + }, + ) + + override fun onAttachView(view: NotificationDebugView) { + super.onAttachView(view) + Timber.i("Notification debug view was initialized") + with(view) { + initView() + setItems(items) + } + } + + private fun withStudent(block: suspend (Student) -> Unit) { + presenterScope.launch { + block(studentRepository.getCurrentStudent(false)) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugView.kt new file mode 100644 index 00000000..e2c043c3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugView.kt @@ -0,0 +1,10 @@ +package io.github.wulkanowy.ui.modules.debug.notification + +import io.github.wulkanowy.ui.base.BaseView + +interface NotificationDebugView : BaseView { + + fun initView() + + fun setItems(notificationDebugs: List) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt new file mode 100644 index 00000000..042cf07e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Attendance +import java.time.LocalDate + +val debugAttendanceItems = listOf( + generateAttendance("Matematyka", "PRESENCE"), + generateAttendance("Język angielski", "UNEXCUSED_LATENESS"), + generateAttendance("Geografia", "ABSENCE_UNEXCUSED"), + generateAttendance("Sieci komputerowe", "ABSENCE_EXCUSED"), + generateAttendance("Systemy operacyjne", "EXCUSED_LATENESS"), + generateAttendance("Język niemiecki", "ABSENCE_UNEXCUSED"), + generateAttendance("Biologia", "ABSENCE_UNEXCUSED"), + generateAttendance("Chemia", "ABSENCE_EXCUSED"), + generateAttendance("Fizyka", "ABSENCE_UNEXCUSED"), + generateAttendance("Matematyka", "ABSENCE_EXCUSED"), +) + +private fun generateAttendance(subject: String, name: String) = Attendance( + subject = subject, + studentId = 0, + diaryId = 0, + date = LocalDate.now(), + timeId = 0, + number = 1, + name = name, + presence = false, + absence = false, + exemption = false, + lateness = false, + excused = false, + deleted = false, + excusable = false, + excuseStatus = "" +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt new file mode 100644 index 00000000..625ff4c9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/conference.kt @@ -0,0 +1,59 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Conference +import java.time.Duration +import java.time.Instant + +val debugConferenceItems = listOf( + generateConference( + title = "Spotkanie z rodzicami/opiekunami", + subject = "Podsumowanie I semestru - średnia klasy, oceny, frekwencja, zachowanie" + ), + generateConference( + title = "ZSW", + subject = "Pierwsze - organizacyjne zebranie z rodzicami klas pierwszych" + ), + generateConference( + title = "Spotkanie z rodzicami w sprawie bójki", + subject = "Pierwsze - i miejmy nadzieję ostatnie - zebranie w takiej sprawie" + ), + generateConference( + title = "Spotkanie z rodzicami w sprawie kolejnej bójki", + subject = "Kolejne - ale miejmy jeszcze nadzieję, że ostatnie - zebranie w takiej sprawie" + ), + generateConference( + title = "Spotkanie z rodzicami w sprawie jeszcze jednej bójki", + subject = "Proszę państwa, proszę uspokoić swoje dzieci" + ), + generateConference( + title = "Spotkanie w sprawie wydalenia części uczniów", + subject = "Proszę państwa, to jest krok ostateczny, którego nikt nie chciał się podjąć, ale ktoś musi" + ), + generateConference( + title = "Spotkanie organizacyjne w drugim semestrze", + subject = "Prezentacja na temat projektu 'Spokojnej szkoły'" + ), + generateConference( + title = "Spotkanie z pierwszakami", + subject = "Mamy sobie do pogadania" + ), + generateConference( + title = "Spotkanie z rodzicami szóstoklaistów", + subject = "Musimy przygotować dzieci do ważnej uroczystości" + ), + generateConference( + title = "Spotkanie podsumowujące pracę w ciągu ostatniego roku szkolnego", + subject = "Proszę państwa, zapraszam serdecznie na spotkanie" + ), +) + +private fun generateConference(title: String, subject: String) = Conference( + title = title, + subject = subject, + studentId = 0, + diaryId = 0, + agenda = "", + conferenceId = 0, + date = Instant.now().plus(Duration.ofMinutes(10)), + presentOnConference = "", +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/exam.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/exam.kt new file mode 100644 index 00000000..45700c29 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/exam.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Exam +import java.time.LocalDate + +val debugExamItems = listOf( + generateExam("Matematyka", "Figury na płaszczyźnie"), + generateExam("Język angielski", "czasowniki nieregularne 1 część"), + generateExam("Geografia", "Opolszczyzna - mapa"), + generateExam("Sieci komputerowe", "Zaciskanie erjotek"), + generateExam("Systemy operacyjne", "Instalacja ubuntu 16.04"), + generateExam("Język niemiecki", "oral exam"), + generateExam("Biologia", "Budowa koniczyny"), + generateExam("Chemia", "synteza płynnego zaliczenia"), + generateExam("Fizyka", "telekineza"), + generateExam("Matematyka", "Liczby zespolone i pochodne piątego rzędu"), +) + +private fun generateExam(subject: String, description: String) = Exam( + subject = subject, + description = description, + studentId = 0, + diaryId = 0, + date = LocalDate.now(), + entryDate = LocalDate.now(), + group = "", + type = "", + teacher = "", + teacherSymbol = "" +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt new file mode 100644 index 00000000..d5a0c089 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt @@ -0,0 +1,48 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.GradeDescriptive + +val debugGradeDescriptiveItems = listOf( + generateGradeDescriptive( + "Matematyka", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."), + generateGradeDescriptive( + "Geografia", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Sieci komputerowe", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Systemy operacyjne", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Język polski", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Język angielski", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."), + generateGradeDescriptive( + "Język niemiecki", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Wychowanie fizyczne", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), +) + +private fun generateGradeDescriptive(subject: String, description: String) = + GradeDescriptive( + semesterId = 0, + studentId = 0, + subject = subject, + description = description + ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt new file mode 100644 index 00000000..77b60188 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Grade +import java.time.LocalDate + +val debugGradeDetailsItems = listOf( + generateGrade("Matematyka", "+"), + generateGrade("Matematyka", "120", comment = "%"), + generateGrade("Fizyka", "-"), + generateGrade("Geografia", "4+"), + generateGrade("Sieci komputerowe", "1"), + generateGrade("Systemy operacyjne", "3+"), + generateGrade("Język polski", "2-"), + generateGrade("Język angielski", "4+"), + generateGrade("Religia", "6"), + generateGrade("Język niemiecki", "1!"), + generateGrade("Wychowanie fizyczne", "5"), +) + +private fun generateGrade(subject: String, entry: String, comment: String = "") = Grade( + subject = subject, + entry = entry, + semesterId = 0, + studentId = 0, + value = 0.0, + modifier = 0.0, + comment = comment, + color = "", + gradeSymbol = "", + description = "", + weight = "", + weightValue = 0.0, + date = LocalDate.now(), + teacher = "" +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt new file mode 100644 index 00000000..e92d1afb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.GradeSummary + +val debugGradeSummaryItems = listOf( + generateSummary("Matematyka", "2-", "2"), + generateSummary("Fizyka", "1", "2"), + generateSummary("Geografia", "4+", "5"), + generateSummary("Sieci komputerowe", "2", "5"), + generateSummary("Systemy operacyjne", "3", "4"), + generateSummary("Język polski", "1", "3"), + generateSummary("Język angielski", "4", "3"), + generateSummary("Religia", "5", "6"), + generateSummary("Język niemiecki", "2", "2"), + generateSummary("Wychowanie fizyczne", "5", "5"), + generateSummary("Biologia", "4", "4"), +) + +private fun generateSummary(subject: String, predicted: String, final: String) = GradeSummary( + semesterId = 0, + studentId = 0, + position = 0, + subject = subject, + predictedGrade = predicted, + finalGrade = final, + proposedPoints = "", + finalPoints = "", + pointsSum = "", + average = .0, + pointsSumAllYear = null, + averageAllYear = null, +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/homework.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/homework.kt new file mode 100644 index 00000000..6c7d7fac --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/homework.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Homework +import java.time.LocalDate + +val debugHomeworkItems = listOf( + generateHomework("Chemia", "Test diagnozujący i Rozdział I do 30.10"), + generateHomework("Etyka", "Notatka własna do zajęć o ks. Jerzym Popiełuszko"), + generateHomework("Język angielski", "Zadania egzaminacyjne"), + generateHomework("Metodologia programowania", "Wszystkie instrukcje"), + generateHomework("Język polski", "Notatka własna na temat Wokulskiego z lektury Lalka"), + generateHomework("Systemy operacyjne", "Sprawozdanie z wykonania ćwiczenia nr 21.137"), + generateHomework("Matematyka", "Zadania od strony 1 do 128"), + generateHomework("Język niemiecki", "Opis swoich wakacji - dialog z kolegą"), + generateHomework("Język angielski", "Opis swoich wakacji - dialog z kolegą"), + generateHomework("Wychowanie fizyczne", "Notatka na temat skoku w dald"), + generateHomework("Biologia", "Notatka na temat grzechotnika"), +) + +private fun generateHomework(subject: String, content: String) = Homework( + subject = subject, + content = content, + semesterId = 0, + studentId = 0, + date = LocalDate.now(), + entryDate = LocalDate.now(), + teacher = "", + teacherSymbol = "", + attachments = listOf(), +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/luckyNumber.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/luckyNumber.kt new file mode 100644 index 00000000..e929a090 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/luckyNumber.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import java.time.LocalDate +import kotlin.random.Random + +val debugLuckyNumber + get() = LuckyNumber( + studentId = 0, + date = LocalDate.now(), + luckyNumber = Random.nextInt(1, 128), + ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt new file mode 100644 index 00000000..27d8613a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Message +import java.time.Instant + +val debugMessageItems = listOf( + generateMessage("Kowalski Jan", "Tytuł"), + generateMessage("Nazwisko Imię", "Tytuł wiadomości"), + generateMessage("Malinowski Kazimierz", "Nakrętki"), + generateMessage("Jastębowszki Orest", "Prośba do uczniów o pomoc przy projekcie"), + generateMessage("Metylowy Oranż", "Pozew o plagiat"), + generateMessage("VULCAN", "Uwaga na nieautoryzowane aplikacje"), + generateMessage("Mama", "Zacznij się w końcu uczyć do matury!!!11"), + generateMessage("Tata", "Kupisz mi coś w sklepie?"), + generateMessage("Wychowawca", "Upomnienie od wychowawcy za nieobecności"), + generateMessage("Kochanowska Joanna", "Poprawa rozprawki - termin"), +) + +private fun generateMessage(sender: String, subject: String) = Message( + subject = subject, + messageId = 123, + email = "", + date = Instant.now(), + folderId = 0, + unread = true, + readBy = 2, + unreadBy = 2, + hasAttachments = false, + messageGlobalKey = "", + correspondents = sender, + mailboxKey = "", +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/note.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/note.kt new file mode 100644 index 00000000..b287a508 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/note.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import java.time.LocalDate + +val debugNoteItems = listOf( + generateNote("Aleksadra Krajewska", "Przeszkadzanie na lekcjach", NoteCategory.NEGATIVE), + generateNote("Zofia Czerwińska", "Udział w konkursie szkolnym", NoteCategory.POSITIVE), + generateNote("Stanisław Krupa", "Kultura języka", NoteCategory.NEUTRAL), + generateNote("Karolina Kowalska", "Wypełnianie obowiązków ucznia", NoteCategory.NEUTRAL), + generateNote("Joanna Krupa", "Umycie tablicy cifem", NoteCategory.POSITIVE), + generateNote("Duchowicz Maksymilian", "Reprezentowanie szkoły", NoteCategory.POSITIVE), + generateNote("Michał Mazur", "Przeszkadzanie na lekcji", NoteCategory.NEGATIVE), + generateNote("Karolina Kowalska", "Wypełnianie obowiązków ucznia", NoteCategory.NEGATIVE), + generateNote("Aleksandra Krajewska", "Wysadzenie klasy w powietrze", NoteCategory.NEGATIVE), +) + +private fun generateNote(teacher: String, category: String, type: NoteCategory) = Note( + teacher = teacher, + category = category, + categoryType = type.id, + studentId = 0, + date = LocalDate.now(), + teacherSymbol = "", + isPointsShow = false, + points = 0, + content = "" +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt new file mode 100644 index 00000000..9b21f08e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt @@ -0,0 +1,24 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import java.time.LocalDate + +val debugSchoolAnnouncementItems = listOf( + generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych
03.05.2021 – poniedziałek"), + generateAnnouncement("Zasady bezpieczeństwa", "Wszyscy uczniowie są zobowiązani do noszenia maseczek"), + generateAnnouncement("Święto szkoły", "W najbliższych dniach obchodzimy święto szkoły, podczas którego..."), + generateAnnouncement("Rocznica odzyskania przez szkołę sztandaru", "Juz niedługo, bo za tydzień, a dokładnie za 8 dni..."), + generateAnnouncement("Ogłoszenie w sprawie otwarcia stołówki", "Wszyscy uczniowie zainteresowani obiadami w szkole..."), + generateAnnouncement("Uczniowie proszeni do sekretariatu", "Kuba i Jacek z klasy czwartej proszeni do dyrektora w trybie pilnym"), + generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych
21.06.2021 – poniedziałek"), + generateAnnouncement("Zasady bezpieczeństwa", "Wszyscy uczniowie są zobowiązani do zdjęcia maseczek"), + generateAnnouncement("Święto państwowe", "W najbliższych dniach obchodzimy święto państwowe, podczas którego..."), + generateAnnouncement("Uczniowie proszeni do sekretariatu", "Kuba i Jacek z klasy czwartej proszeni do dyrektora w trybie wolnym"), +) + +private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement( + subject = subject, + content = content, + studentId = 0, + date = LocalDate.now() +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt new file mode 100644 index 00000000..ff968654 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.Duration +import java.time.Instant +import java.time.LocalDate +import kotlin.random.Random + +val debugTimetableItems = listOf( + generateTimetable("Matematyka", "12", "01"), + generateTimetable("Język angielski", "23", "12"), + generateTimetable("Geografia", "34", "23"), + generateTimetable("Sieci komputerowe", "45", "34"), + generateTimetable("Systemy operacyjne", "56", "45"), + generateTimetable("Język niemiecki", "67", "56"), + generateTimetable("Biologia", "78", "67"), + generateTimetable("Chemia", "89", "78"), + generateTimetable("Fizyka", "90", "89"), + generateTimetable("Matematyka", "01", "90"), +) + +private fun generateTimetable(subject: String, room: String, roomOld: String) = Timetable( + subject = subject, + studentId = 0, + diaryId = 0, + date = LocalDate.now().minusDays(Random.nextLong(0, 8)), + number = 1, + start = Instant.now().plus(Duration.ofHours(1)), + end = Instant.now(), + subjectOld = "", + group = "", + room = room, + roomOld = roomOld, + teacher = "Wtorkowska Renata", + teacherOld = "", + info = "", + isStudentPlan = true, + changes = true, + canceled = true +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt new file mode 100644 index 00000000..4dc8afd3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamAdapter.kt @@ -0,0 +1,66 @@ +package io.github.wulkanowy.ui.modules.exam + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.HeaderExamBinding +import io.github.wulkanowy.databinding.ItemExamBinding +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import java.time.LocalDate +import javax.inject.Inject + +class ExamAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Exam) -> Unit = {} + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ExamItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderExamBinding.inflate(inflater, parent, false)) + ExamItem.ViewType.ITEM.id -> ItemViewHolder(ItemExamBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Exam) + } + } + + @SuppressLint("DefaultLocale") + private fun bindHeaderViewHolder(binding: HeaderExamBinding, date: LocalDate) { + with(binding) { + examHeaderDay.text = date.weekDayName.capitalise() + examHeaderDate.text = date.toFormattedString() + } + } + + private fun bindItemViewHolder(binding: ItemExamBinding, exam: Exam) { + with(binding) { + examItemSubject.text = exam.subject + examItemTeacher.text = exam.teacher + examItemType.text = exam.type + + root.setOnClickListener { onClickListener(exam) } + } + } + + private class HeaderViewHolder(val binding: HeaderExamBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemExamBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index ed5092c9..d452d74a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -1,50 +1,67 @@ package io.github.wulkanowy.ui.modules.exam +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.DialogExamBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.openCalendarEventAdd +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_exam.* +import java.time.LocalTime -class ExamDialog : DialogFragment() { +@AndroidEntryPoint +class ExamDialog : BaseDialogFragment() { private lateinit var exam: Exam companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Exam): ExamDialog { - return ExamDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } - } + fun newInstance(exam: Exam) = ExamDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to exam) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - exam = getSerializable(ARGUMENT_KEY) as Exam + exam = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogExamBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + examDialogSubjectValue.text = exam.subject + examDialogTypeValue.text = exam.type + examDialogTeacherValue.text = exam.teacher + examDialogEntryDateValue.text = exam.entryDate.toFormattedString() + examDialogDeadlineDateValue.text = exam.date.toFormattedString() + examDialogDescriptionValue.text = exam.description.ifBlank { + getString(R.string.all_no_data) + } + + examDialogClose.setOnClickListener { dismiss() } + examDialogAddToCalendar.setOnClickListener { + requireContext().openCalendarEventAdd( + title = "${exam.subject} - ${exam.type}", + description = exam.description, + start = exam.date.atTime(LocalTime.of(8, 0)), + end = exam.date.atTime(LocalTime.of(8, 45)), + ) + } } } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_exam, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - examDialogSubjectValue.text = exam.subject - examDialogTypeValue.text = exam.type - examDialogTeacherValue.text = exam.teacher - examDialogDateValue.text = exam.entryDate.toFormattedString() - examDialogDescriptionValue.text = exam.description - - examDialogClose.setOnClickListener { dismiss() } - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index b880f465..0123e234 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -1,33 +1,30 @@ package io.github.wulkanowy.ui.modules.exam import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import android.view.View.* +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.databinding.FragmentExamBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_exam.* +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.TitledView { +@AndroidEntryPoint +class ExamFragment : BaseFragment(R.layout.fragment_exam), ExamView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: ExamPresenter @Inject - lateinit var examAdapter: FlexibleAdapter> + lateinit var examAdapter: ExamAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -37,97 +34,102 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. override val titleStringId get() = R.string.exam_title - override val isViewEmpty get() = examAdapter.isEmpty + override val isViewEmpty get() = examAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_exam, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = examRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentExamBinding.bind(view) + messageContainer = binding.examRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { - examAdapter.setOnItemClickListener(presenter::onExamItemSelected) + examAdapter.onClickListener = presenter::onExamItemSelected - with(examRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.examRecycler) { + layoutManager = LinearLayoutManager(context) adapter = examAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider(R.layout.item_exam) - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } - examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - examErrorRetry.setOnClickListener { presenter.onRetry() } - examErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + examSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + examSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + examErrorRetry.setOnClickListener { presenter.onRetry() } + examErrorDetails.setOnClickListener { presenter.onDetailsClick() } - examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } - examNextButton.setOnClickListener { presenter.onNextWeek() } + examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } + examNextButton.setOnClickListener { presenter.onNextWeek() } - examNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + examNavContainer.elevation = requireContext().dpToPx(3f) + } } - override fun hideRefresh() { - examSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.examSwipe.isRefreshing = show } - override fun updateData(data: List) { - examAdapter.updateDataSet(data, true) + override fun updateData(data: List>) { + with(examAdapter) { + items = data + notifyDataSetChanged() + } } override fun updateNavigationWeek(date: String) { - examNavDate.text = date + binding.examNavDate.text = date } override fun clearData() { - examAdapter.clear() + with(examAdapter) { + items = emptyList() + notifyDataSetChanged() + } } - override fun resetView() { - examRecycler.scrollToPosition(0) + override fun showEmpty(show: Boolean) { + binding.examEmpty.visibility = if (show) VISIBLE else GONE + } + + override fun showErrorView(show: Boolean) { + binding.examError.visibility = if (show) VISIBLE else GONE + } + + override fun setErrorDetails(message: String) { + binding.examErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.examProgress.visibility = if (show) VISIBLE else GONE + } + + override fun enableSwipe(enable: Boolean) { + binding.examSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.examRecycler.visibility = if (show) VISIBLE else GONE + } + + override fun showPreButton(show: Boolean) { + binding.examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showNextButton(show: Boolean) { + binding.examNextButton.visibility = if (show) VISIBLE else INVISIBLE + } + + override fun showExamDialog(exam: Exam) { + (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam)) } override fun onFragmentReselected() { if (::presenter.isInitialized) presenter.onViewReselected() } - override fun showEmpty(show: Boolean) { - examEmpty.visibility = if (show) VISIBLE else GONE - } - - override fun showErrorView(show: Boolean) { - examError.visibility = if (show) VISIBLE else GONE - } - - override fun setErrorDetails(message: String) { - examErrorMessage.text = message - } - - override fun showProgress(show: Boolean) { - examProgress.visibility = if (show) VISIBLE else GONE - } - - override fun enableSwipe(enable: Boolean) { - examSwipe.isEnabled = enable - } - - override fun showContent(show: Boolean) { - examRecycler.visibility = if (show) VISIBLE else GONE - } - - override fun showPreButton(show: Boolean) { - examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE - } - - override fun showNextButton(show: Boolean) { - examNextButton.visibility = if (show) VISIBLE else INVISIBLE - } - - override fun showExamDialog(exam: Exam) { - (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam)) + override fun resetView() { + binding.examRecycler.smoothScrollToPosition(0) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt deleted file mode 100644 index 0a5b862c..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamHeader.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.wulkanowy.ui.modules.exam - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractHeaderItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.ExpandableViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.weekDayName -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_exam.* -import org.threeten.bp.LocalDate - -class ExamHeader(private val date: LocalDate) : AbstractHeaderItem() { - - override fun getLayoutRes() = R.layout.header_exam - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, - position: Int, payloads: MutableList?) { - holder.run { - examHeaderDay.text = date.weekDayName.capitalize() - examHeaderDate.text = date.toFormattedString() - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExamHeader - - if (date != other.date) return false - - return true - } - - override fun hashCode(): Int { - return date.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt index 8971b4df..579e3720 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamItem.kt @@ -1,50 +1,9 @@ package io.github.wulkanowy.ui.modules.exam -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractSectionableItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Exam -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_exam.* +data class ExamItem(val value: T, val viewType: ViewType) { -class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem(header) { - - override fun getLayoutRes() = R.layout.item_exam - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.run { - examItemSubject.text = exam.subject - examItemTeacher.text = exam.teacher - examItemType.text = exam.type - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExamItem - - if (exam != other.exam) return false - - return true - } - - override fun hashCode(): Int { - var result = exam.hashCode() - result = 31 * result + exam.id.toInt() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index aac9bc4b..85814072 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,35 +1,29 @@ package io.github.wulkanowy.ui.modules.exam -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.repositories.exam.ExamRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.toFormattedString -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.ofEpochDay +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.LocalDate.ofEpochDay import javax.inject.Inject class ExamPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val examRepository: ExamRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = now().nextOrSameSchoolDay @@ -43,24 +37,24 @@ class ExamPresenter @Inject constructor( view.initView() Timber.i("Exam view was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(ofEpochDay(date ?: baseDate.toEpochDay())) + reloadView(ofEpochDay(date ?: baseDate.toEpochDay())) + loadData() if (currentDate.isHolidays) setBaseDateOnHolidays() - reloadView() } fun onPreviousWeek() { - loadData(currentDate.minusDays(7)) - reloadView() + reloadView(currentDate.minusDays(7)) + loadData() } fun onNextWeek() { - loadData(currentDate.plusDays(7)) - reloadView() + reloadView(currentDate.plusDays(7)) + loadData() } fun onSwipeRefresh() { Timber.i("Force refreshing the exam") - loadData(currentDate, true) + loadData(true) } fun onRetry() { @@ -68,81 +62,73 @@ class ExamPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(currentDate, true) + loadData(true) } fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } - fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is ExamItem) { - Timber.i("Select exam item ${item.exam.id}") - view?.showExamDialog(item.exam) - } - } - - fun onViewReselected() { - Timber.i("Exam view is reselected") - baseDate.also { - if (currentDate != it) { - loadData(it) - reloadView() - } else if (view?.isViewEmpty == false) view?.resetView() - } + fun onExamItemSelected(exam: Exam) { + Timber.i("Select exam item ${exam.id}") + view?.showExamDialog(exam) } private fun setBaseDateOnHolidays() { - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) currentDate = baseDate reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + } + .launch("holidays") } - private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading exam data started") - currentDate = date - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh) - } - } - .delay(200, MILLISECONDS) - .map { it.groupBy { exam -> exam.date }.toSortedMap() } - .map { createExamItems(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading exam result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - } - analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading exam result: An exception occurred") - errorHandler.dispatch(it) - }) + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + examRepository.getExams( + student = student, + semester = semester, + start = currentDate.monday, + end = currentDate.sunday, + forceRefresh = forceRefresh + ) } + .logResourceStatus("load exam data") + .mapResourceData { createExamItems(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "exam", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -156,15 +142,17 @@ class ExamPresenter @Inject constructor( } } - private fun createExamItems(items: Map>): List { - return items.flatMap { - ExamHeader(it.key).let { header -> - it.value.reversed().map { item -> ExamItem(header, item) } + private fun createExamItems(items: List): List> { + return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> + listOf(ExamItem(date, ExamItem.ViewType.HEADER)) + exams.reversed().map { exam -> + ExamItem(exam, ExamItem.ViewType.ITEM) } - } + }.flatten() } - private fun reloadView() { + private fun reloadView(date: LocalDate) { + currentDate = date + Timber.i("Reload exam view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) @@ -181,8 +169,23 @@ class ExamPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.friday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) + } + } + + fun onViewReselected() { + Timber.i("Exam view is reselected") + + baseDate = now().nextOrSameSchoolDay + + if (currentDate != baseDate) { + reloadView(baseDate) + loadData() + } else if (view?.isViewEmpty == false) { + view?.resetView() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt index 5f4a7430..677fac40 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt @@ -9,15 +9,13 @@ interface ExamView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun updateNavigationWeek(date: String) fun clearData() - fun hideRefresh() - - fun resetView() + fun showRefresh(show: Boolean) fun showEmpty(show: Boolean) @@ -36,4 +34,6 @@ interface ExamView : BaseView { fun showPreButton(show: Boolean) fun showExamDialog(exam: Exam) + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt new file mode 100644 index 00000000..3158bd0b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.ui.modules.grade + +enum class GradeAverageMode(val value: String) { + ALL_YEAR("all_year"), + ONE_SEMESTER("only_one_semester"), + BOTH_SEMESTERS("both_semesters"); + + companion object { + fun getByValue(value: String) = entries.firstOrNull { it.value == value } ?: ONE_SEMESTER + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 3bb084d3..7f14c01f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,73 +1,318 @@ package io.github.wulkanowy.ui.modules.grade +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.errorOrNull +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.mapData +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier -import io.reactivex.Maybe -import io.reactivex.Single +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import javax.inject.Inject +@OptIn(ExperimentalCoroutinesApi::class) class GradeAverageProvider @Inject constructor( - private val preferencesRepository: PreferencesRepository, + private val semesterRepository: SemesterRepository, private val gradeRepository: GradeRepository, - private val gradeSummaryRepository: GradeSummaryRepository + private val preferencesRepository: PreferencesRepository ) { - private val plusModifier = preferencesRepository.gradePlusModifier + private data class AverageCalcParams( + val gradeAverageMode: GradeAverageMode, + val forceAverageCalc: Boolean, + val isOptionalArithmeticAverage: Boolean, + val plusModifier: Double, + val minusModifier: Double, + ) - private val minusModifier = preferencesRepository.gradeMinusModifier + fun getGradesDetailsWithAverage( + student: Student, + semesterId: Int, + forceRefresh: Boolean + ): Flow>> = combine( + flow = preferencesRepository.gradeAverageModeFlow, + flow2 = preferencesRepository.gradeAverageForceCalcFlow, + flow3 = preferencesRepository.isOptionalArithmeticAverageFlow, + flow4 = preferencesRepository.gradePlusModifierFlow, + flow5 = preferencesRepository.gradeMinusModifierFlow, + ) { gradeAverageMode, forceAverageCalc, isOptionalArithmeticAverage, plusModifier, minusModifier -> + AverageCalcParams( + gradeAverageMode = gradeAverageMode, + forceAverageCalc = forceAverageCalc, + isOptionalArithmeticAverage = isOptionalArithmeticAverage, + plusModifier = plusModifier, + minusModifier = minusModifier, + ) + }.flatMapLatest { params -> + flatResourceFlow { + val semesters = semesterRepository.getSemesters(student) + when (params.gradeAverageMode) { + ONE_SEMESTER -> getGradeSubjects( + student = student, + semester = semesters.first { it.semesterId == semesterId }, + forceRefresh = forceRefresh, + params = params, + ) - fun getGradeAverage(student: Student, semesters: List, selectedSemesterId: Int, forceRefresh: Boolean): Single>> { - return when (preferencesRepository.gradeAverageMode) { - "all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh) - "only_one_semester" -> getOnlyOneSemesterAverage(student, semesters, selectedSemesterId, forceRefresh) - else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ") + BOTH_SEMESTERS -> calculateCombinedAverage( + student = student, + semesters = semesters, + semesterId = semesterId, + forceRefresh = forceRefresh, + config = params, + ) + + ALL_YEAR -> calculateCombinedAverage( + student = student, + semesters = semesters, + semesterId = semesterId, + forceRefresh = forceRefresh, + config = params, + ) + } + } + }.distinctUntilChanged() + + private fun calculateCombinedAverage( + student: Student, + semesters: List, + semesterId: Int, + forceRefresh: Boolean, + config: AverageCalcParams, + ): Flow>> { + val selectedSemester = semesters.single { it.semesterId == semesterId } + val firstSemester = + semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } + + val selectedSemesterGradeSubjects = + getGradeSubjects(student, selectedSemester, forceRefresh, config) + + if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects + + val firstSemesterGradeSubjects = + getGradeSubjects(student, firstSemester, forceRefresh, config) + + return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> + if (firstSemesterGradeSubject.errorOrNull != null) { + return@combine firstSemesterGradeSubject + } + + val isAnyVulcanAverageInFirstSemester = + firstSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } + val isAnyVulcanAverageInSecondSemester = + secondSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } + + val updatedData = secondSemesterGradeSubject.dataOrNull?.map { secondSemesterSubject -> + val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() + .singleOrNull { it.subject == secondSemesterSubject.subject } + + val updatedAverage = if (config.gradeAverageMode == ALL_YEAR) { + calculateAllYearAverage( + student = student, + isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, + secondSemesterSubject = secondSemesterSubject, + firstSemesterSubject = firstSemesterSubject, + config = config, + ) + } else { + calculateBothSemestersAverage( + student = student, + isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, + secondSemesterSubject = secondSemesterSubject, + firstSemesterSubject = firstSemesterSubject, + config = config + ) + } + secondSemesterSubject.copy(average = updatedAverage) + } + secondSemesterGradeSubject.mapData { updatedData!! } } } - private fun getAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single>> { - val selectedSemester = semesters.single { it.semesterId == semesterId } - val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } + private fun calculateAllYearAverage( + student: Student, + isAnyVulcanAverage: Boolean, + secondSemesterSubject: GradeSubject, + firstSemesterSubject: GradeSubject?, + config: AverageCalcParams, + ) = if (!isAnyVulcanAverage || config.forceAverageCalc) { + val updatedSecondSemesterGrades = secondSemesterSubject.grades + .updateModifiers(student, config) + val updatedFirstSemesterGrades = firstSemesterSubject?.grades + ?.updateModifiers(student, config).orEmpty() - return getAverageFromGradeSummary(student, selectedSemester, forceRefresh) - .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) - .flatMap { firstGrades -> - if (selectedSemester == firstSemester) Single.just(firstGrades) - else { - gradeRepository.getGrades(student, firstSemester) - .map { secondGrades -> secondGrades + firstGrades } + (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage( + isOptionalArithmeticAverage = config.isOptionalArithmeticAverage, + ) + } else { + secondSemesterSubject.average + } + + private fun calculateBothSemestersAverage( + student: Student, + isAnyVulcanAverage: Boolean, + secondSemesterSubject: GradeSubject, + firstSemesterSubject: GradeSubject?, + config: AverageCalcParams, + ): Double { + return if (!isAnyVulcanAverage || config.forceAverageCalc) { + val isSecondSemesterHasWeightGrade = secondSemesterSubject.grades + .any { it.weightValue > .0 } + val isSecondSemesterHasArithmeticGrade = secondSemesterSubject.grades + .all { it.weightValue == .0 } && config.isOptionalArithmeticAverage + val isSecondSemesterHaveAverage = + isSecondSemesterHasWeightGrade || isSecondSemesterHasArithmeticGrade + + val divider = if (isSecondSemesterHaveAverage) 2 else 1 + val secondSemesterAverage = secondSemesterSubject.grades + .updateModifiers(student, config) + .calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage) + val firstSemesterAverage = firstSemesterSubject?.grades + ?.updateModifiers(student, config) + ?.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage) + ?: secondSemesterAverage + + (secondSemesterAverage + firstSemesterAverage) / divider + } else { + val divider = if (secondSemesterSubject.average > 0) 2 else 1 + + secondSemesterSubject.average.plus( + (firstSemesterSubject?.average ?: secondSemesterSubject.average) + ) / divider + } + } + + private fun getGradeSubjects( + student: Student, + semester: Semester, + forceRefresh: Boolean, + params: AverageCalcParams, + ): Flow>> { + return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) + .mapResourceData { res -> + val (details, summaries, descriptives) = res + val isAnyAverage = summaries.any { it.average != .0 } + val allGrades = details.groupBy { it.subject } + val descriptiveGradesBySubject = descriptives.associateBy { it.subject } + + val items = summaries + .createEmptySummariesByGradesIfNeeded( + student = student, + semester = semester, + grades = allGrades.toList(), + calcAverage = isAnyAverage, + params = params, + ) + .createEmptySummariesByDescriptiveGradesIfNeeded( + student = student, + semester = semester, + descriptives = descriptives, + ) + .map { summary -> + val grades = allGrades[summary.subject].orEmpty() + val descriptiveGrade = descriptiveGradesBySubject[summary.subject] + + GradeSubject( + subject = summary.subject, + average = if (!isAnyAverage || params.forceAverageCalc) { + grades.updateModifiers(student, params) + .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage) + } else summary.average, + points = summary.pointsSum, + summary = summary, + grades = grades, + descriptive = descriptiveGrade, + isVulcanAverage = isAnyAverage + ) } - }.map { grades -> - grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } - .groupBy { it.subject } - .map { Triple(it.key, it.value.calcAverage(), "") } - }) + + items + } } - private fun getOnlyOneSemesterAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Single>> { - val selectedSemester = semesters.single { it.semesterId == semesterId } + private fun List.createEmptySummariesByDescriptiveGradesIfNeeded( + student: Student, + semester: Semester, + descriptives: List + ): List { + val summarySubjects = this.map { it.subject } + val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive -> + if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null - return getAverageFromGradeSummary(student, selectedSemester, forceRefresh) - .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) - .map { grades -> - grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } - .groupBy { it.subject } - .map { Triple(it.key, it.value.calcAverage(), "") } - }) + GradeSummary( + studentId = student.studentId, + semesterId = semester.semesterId, + position = 0, + subject = gradeDescriptive.subject, + predictedGrade = "", + finalGrade = "", + proposedPoints = "", + finalPoints = "", + pointsSum = "", + pointsSumAllYear = null, + average = .0, + averageAllYear = null, + ) + } + + return this + gradeSummaryToAdd } - private fun getAverageFromGradeSummary(student: Student, selectedSemester: Semester, forceRefresh: Boolean): Maybe>> { - return gradeSummaryRepository.getGradesSummary(student, selectedSemester, forceRefresh) - .toMaybe() - .flatMap { - if (it.any { summary -> summary.average != .0 }) { - Maybe.just(it.map { summary -> Triple(summary.subject, summary.average, summary.pointsSum) }) - } else Maybe.empty() - }.filter { !preferencesRepository.gradeAverageForceCalc } + private fun List.createEmptySummariesByGradesIfNeeded( + student: Student, + semester: Semester, + grades: List>>, + calcAverage: Boolean, + params: AverageCalcParams, + ): List { + if (isNotEmpty() && size > grades.size) return this + + return grades.mapIndexed { i, (subject, details) -> + singleOrNull { it.subject == subject }?.let { return@mapIndexed it } + GradeSummary( + studentId = student.studentId, + semesterId = semester.semesterId, + position = i, + subject = subject, + predictedGrade = "", + finalGrade = "", + proposedPoints = "", + finalPoints = "", + pointsSum = "", + pointsSumAllYear = null, + average = when { + calcAverage -> details + .updateModifiers(student, params) + .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage) + + else -> .0 + }, + averageAllYear = null, + ) + } } + + private fun List.updateModifiers( + student: Student, + params: AverageCalcParams, + ): List = if (student.loginMode == Sdk.Mode.SCRAPPER.name) { + map { it.changeModifier(params.plusModifier, params.minusModifier) } + } else this } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index b1faf18f..cc61dc25 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -1,16 +1,18 @@ package io.github.wulkanowy.ui.modules.grade import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.databinding.FragmentGradeBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment @@ -19,21 +21,18 @@ import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_grade.* import javax.inject.Inject -class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainView.TitledView { +@AndroidEntryPoint +class GradeFragment : BaseFragment(R.layout.fragment_grade), GradeView, + MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: GradePresenter - @Inject - lateinit var pagerAdapter: BaseFragmentPagerAdapter - private var semesterSwitchMenu: MenuItem? = null companion object { - private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER" fun newInstance() = GradeFragment() } @@ -42,20 +41,20 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie override var subtitleString = "" - override val currentPageIndex get() = gradeViewPager.currentItem + override val currentPageIndex get() = binding.gradeViewPager.currentItem + private var pagerAdapter: BaseFragmentPagerAdapter? = null + + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_grade, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY)) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeBinding.bind(view) + presenter.onAttachView(this) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -65,28 +64,47 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie } override fun initView() { - with(pagerAdapter) { - containerId = gradeViewPager.id - addFragmentsWithTitle(mapOf( - GradeDetailsFragment.newInstance() to getString(R.string.all_details), - GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary), - GradeStatisticsFragment.newInstance() to getString(R.string.grade_menu_statistics) - )) + with(binding) { + gradeErrorRetry.setOnClickListener { presenter.onRetry() } + gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } } + } - with(gradeViewPager) { + override fun initTabs(pageCount: Int) { + pagerAdapter = BaseFragmentPagerAdapter( + lifecycle = lifecycle, + pagesCount = pageCount, + fragmentManager = childFragmentManager + ) + + with(binding.gradeViewPager) { adapter = pagerAdapter offscreenPageLimit = 3 setOnSelectPageListener(presenter::onPageSelected) } - with(gradeTabLayout) { - setupWithViewPager(gradeViewPager) - setElevationCompat(context.dpToPx(4f)) + with(pagerAdapter!!) { + containerId = binding.gradeViewPager.id + titleFactory = { + when (it) { + 0 -> getString(R.string.all_details) + 1 -> getString(R.string.grade_menu_summary) + 2 -> getString(R.string.grade_menu_statistics) + else -> throw IllegalStateException() + } + } + itemFactory = { + when (it) { + 0 -> GradeDetailsFragment.newInstance() + 1 -> GradeSummaryFragment.newInstance() + 2 -> GradeStatisticsFragment.newInstance() + else -> throw IllegalStateException() + } + } + TabLayoutMediator(binding.gradeTabLayout, binding.gradeViewPager, this).attach() } - gradeErrorRetry.setOnClickListener { presenter.onRetry() } - gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } + binding.gradeTabLayout.elevation = requireContext().dpToPx(4f) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -99,33 +117,33 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie } override fun showContent(show: Boolean) { - gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE - gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE + gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } } override fun showProgress(show: Boolean) { - gradeProgress.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeProgress.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeError.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeError.visibility = if (show) VISIBLE else INVISIBLE } override fun setErrorDetails(message: String) { - gradeErrorMessage.text = message + binding.gradeErrorMessage.text = message } override fun showSemesterSwitch(show: Boolean) { semesterSwitchMenu?.isVisible = show } - override fun showSemesterDialog(selectedIndex: Int) { - val choices = arrayOf( - getString(R.string.grade_semester, 1), - getString(R.string.grade_semester, 2) - ) + override fun showSemesterDialog(selectedIndex: Int, semesters: List) { + val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) } + .toTypedArray() - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setSingleChoiceItems(choices, selectedIndex) { dialog, which -> presenter.onSemesterSelected(which) dialog.dismiss() @@ -137,7 +155,10 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie override fun setCurrentSemesterName(semester: Int, schoolYear: Int) { subtitleString = getString(R.string.grade_subtitle, semester, schoolYear, schoolYear + 1) - (activity as MainView).setViewSubTitle(subtitleString) + + if (isVisible) { + (activity as MainView?)?.setViewSubTitle(subtitleString) + } } fun onChildRefresh() { @@ -149,24 +170,21 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie } override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentLoadData(semesterId, forceRefresh) + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView) + ?.onParentLoadData(semesterId, forceRefresh) } override fun notifyChildParentReselected(index: Int) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() } override fun notifyChildSemesterChange(index: Int) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putInt(SAVED_SEMESTER_KEY, presenter.selectedIndex) + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() } override fun onDestroyView() { - super.onDestroyView() + pagerAdapter = null presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt deleted file mode 100644 index 342f356c..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade - -import dagger.Module -import dagger.Provides -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.di.scopes.PerChildFragment -import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter -import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment -import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment -import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment - -@Suppress("unused") -@Module -abstract class GradeModule { - - companion object { - - @PerFragment - @Provides - fun provideGradeAdapter(fragment: GradeFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager) - } - - @PerChildFragment - @ContributesAndroidInjector - abstract fun bindGradeDetailsFragment(): GradeDetailsFragment - - @PerChildFragment - @ContributesAndroidInjector - abstract fun binGradeSummaryFragment(): GradeSummaryFragment - - @PerChildFragment - @ContributesAndroidInjector - abstract fun binGradeStatisticsFragment(): GradeStatisticsFragment -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 78885ebd..8a70b3c1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -1,39 +1,35 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.getCurrentOrLast import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class GradePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { - - var selectedIndex = 0 - private set + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + private var selectedIndex = 0 private var schoolYear = 0 - - private var semesters = emptyList() - + private var availableSemesters = emptyList() private val loadedSemesterId = mutableMapOf() private lateinit var lastError: Throwable - fun onAttachView(view: GradeView, savedIndex: Int?) { + override fun onAttachView(view: GradeView) { super.onAttachView(view) - selectedIndex = savedIndex ?: 0 view.initView() Timber.i("Grade view was initialized with $selectedIndex index") errorHandler.showErrorMessage = ::showErrorViewOnError @@ -41,7 +37,7 @@ class GradePresenter @Inject constructor( } fun onCreateMenu() { - if (semesters.isEmpty()) view?.showSemesterSwitch(false) + if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false) } fun onViewReselected() { @@ -50,7 +46,9 @@ class GradePresenter @Inject constructor( } fun onSemesterSwitch(): Boolean { - if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex - 1) + if (availableSemesters.isNotEmpty()) { + view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2)) + } return true } @@ -82,7 +80,7 @@ class GradePresenter @Inject constructor( } fun onPageSelected(index: Int) { - if (semesters.isNotEmpty()) loadChild(index) + if (availableSemesters.isNotEmpty()) loadChild(index) } fun onRetry() { @@ -98,29 +96,34 @@ class GradePresenter @Inject constructor( } private fun loadData() { - Timber.i("Loading grade data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it, refreshOnNoCurrent = true) } - .delay(200, MILLISECONDS) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - val current = it.getCurrentOrLast() - selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex - schoolYear = current.schoolYear - semesters = it.filter { semester -> semester.diaryId == current.diaryId } - view?.setCurrentSemesterName(current.semesterName, schoolYear) + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) + + student to semesters + } + .logResourceStatus("load grade data") + .onResourceData { (student, semesters) -> + val currentSemester = semesters.getCurrentOrLast() + selectedIndex = + if (selectedIndex == 0) currentSemester.semesterName else selectedIndex + schoolYear = currentSemester.schoolYear + availableSemesters = semesters.filter { semester -> + semester.diaryId == currentSemester.diaryId + } view?.run { - Timber.i("Loading grade result: Attempt load index $currentPageIndex") + initTabs(if (student.isEduOne == true) 2 else 3) + setCurrentSemesterName(currentSemester.semesterName, schoolYear) + + Timber.i("Loading grade data: Attempt load index $currentPageIndex") loadChild(currentPageIndex) showErrorView(false) showSemesterSwitch(true) } - }) { - Timber.i("Loading grade result: An exception occurred") - errorHandler.dispatch(it) - }) + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -133,11 +136,18 @@ class GradePresenter @Inject constructor( } private fun loadChild(index: Int, forceRefresh: Boolean = false) { - semesters.first { it.semesterName == selectedIndex }.semesterId.also { - if (forceRefresh || loadedSemesterId[index] != it) { - Timber.i("Load grade child view index: $index") - view?.notifyChildLoadData(index, it, forceRefresh) - } + Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}") + + val newSelectedSemesterId = try { + availableSemesters.first { it.semesterName == selectedIndex }.semesterId + } catch (e: NoSuchElementException) { + Timber.e(e, "Selected semester no exists") + return + } + + if (forceRefresh || loadedSemesterId[index] != newSelectedSemesterId) { + Timber.i("Load grade child view index: $index") + view?.notifyChildLoadData(index, newSelectedSemesterId, forceRefresh) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt new file mode 100644 index 00000000..a465551f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.ui.modules.grade + +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary + +data class GradeSubject( + val subject: String, + val average: Double, + val points: String, + val summary: GradeSummary, + val descriptive: GradeDescriptive?, + val grades: List, + val isVulcanAverage: Boolean +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt index 7b52daa8..fc06c480 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.ui.base.BaseView interface GradeView : BaseView { @@ -8,6 +9,8 @@ interface GradeView : BaseView { fun initView() + fun initTabs(pageCount: Int) + fun showContent(show: Boolean) fun showProgress(show: Boolean) @@ -18,7 +21,7 @@ interface GradeView : BaseView { fun showSemesterSwitch(show: Boolean) - fun showSemesterDialog(selectedIndex: Int) + fun showSemesterDialog(selectedIndex: Int, semesters: List) fun setCurrentSemesterName(semester: Int, schoolYear: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt new file mode 100644 index 00000000..bcbd2df2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -0,0 +1,273 @@ +package io.github.wulkanowy.ui.modules.grade.details + +import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.content.res.Resources +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.NO_POSITION +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.enums.GradeExpandMode +import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding +import io.github.wulkanowy.databinding.ItemGradeDetailsBinding +import io.github.wulkanowy.ui.base.BaseExpandableAdapter +import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.toFormattedString +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() { + + private var headers = mutableListOf() + + private var items = mutableListOf() + + private val expandedPositions = BitSet(items.size) + + private var expandMode = GradeExpandMode.ONE + + var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> } + + lateinit var gradeColorTheme: GradeColorTheme + + fun setDataItems(data: List, expandMode: GradeExpandMode = this.expandMode) { + headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList() + items = + (if (expandMode != GradeExpandMode.ALWAYS_EXPANDED) headers else data).toMutableList() + this.expandMode = expandMode + expandedPositions.clear() + } + + fun updateDetailsItem(position: Int, grade: Grade) { + items[position] = GradeDetailsItem(grade, ViewType.ITEM) + notifyItemChanged(position) + } + + fun getHeaderItem(subject: String): GradeDetailsItem { + val candidates = headers.filter { (it.value as GradeDetailsHeader).subject == subject } + + if (candidates.size > 1) { + Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPositions. Items: $candidates") + } + + return candidates.first() + } + + fun updateHeaderItem(item: GradeDetailsItem) { + val headerPosition = headers.indexOf(item) + val itemPosition = items.indexOf(item) + + headers[headerPosition] = item + items[itemPosition] = item + notifyItemChanged(itemPosition) + } + + fun collapseAll() { + if (!expandedPositions.isEmpty) { + refreshList(headers.toMutableList()) + expandedPositions.clear() + } + } + + @Synchronized + private fun refreshList(newItems: MutableList) { + val diffCallback = GradeDetailsDiffUtil(items, newItems) + val diffResult = DiffUtil.calculateDiff(diffCallback) + items = newItems + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder( + HeaderGradeDetailsBinding.inflate(inflater, parent, false) + ) + + ViewType.ITEM.id -> ItemViewHolder( + ItemGradeDetailsBinding.inflate(inflater, parent, false) + ) + + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder( + holder = holder, + header = items[position].value as GradeDetailsHeader, + position = position + ) + + is ItemViewHolder -> bindItemViewHolder( + holder = holder, + grade = items[position].value as Grade + ) + } + } + + private fun bindHeaderViewHolder( + holder: HeaderViewHolder, + header: GradeDetailsHeader, + position: Int + ) { + val context = holder.binding.root.context + val item = items[position] + val headerPosition = headers.indexOf(item) + + with(holder.binding) { + gradeHeaderDivider.isVisible = holder.bindingAdapterPosition != 0 + with(gradeHeaderSubject) { + text = header.subject + maxLines = if (expandedPositions[headerPosition]) 2 else 1 + } + gradeHeaderAverage.text = formatAverage(header.average, root.context.resources) + with(gradeHeaderAverageAllYear) { + isVisible = header.averageAllYear != null && header.averageAllYear != .0 + text = formatAverageAllYear(header.averageAllYear, root.context.resources) + } + gradeHeaderPointsSum.text = + context.getString(R.string.grade_points_sum, header.pointsSum) + gradeHeaderPointsSum.isVisible = !header.pointsSum.isNullOrEmpty() + gradeHeaderNumber.text = context.resources.getQuantityString( + R.plurals.grade_number_item, + header.grades.size, + header.grades.size + ) + gradeHeaderNote.isVisible = header.newGrades > 0 + + if (header.newGrades > 0) { + gradeHeaderNote.text = header.newGrades.toString() + } + + gradeHeaderContainer.isEnabled = expandMode != GradeExpandMode.ALWAYS_EXPANDED + gradeHeaderContainer.setOnClickListener { + expandGradeHeader(headerPosition, header, holder) + } + } + } + + private fun expandGradeHeader( + headerPosition: Int, + header: GradeDetailsHeader, + holder: HeaderViewHolder + ) { + if (expandMode == GradeExpandMode.ONE) { + val isHeaderExpanded = expandedPositions[headerPosition] + + expandedPositions.clear() + + if (!isHeaderExpanded) { + val updatedItemList = headers.toMutableList() + .apply { addAll(headerPosition + 1, header.grades) } + + expandedPositions.set(headerPosition) + refreshList(updatedItemList) + scrollToHeaderWithSubItems(headerPosition, header.grades.size) + } else { + refreshList(headers.toMutableList()) + } + } else if (expandMode == GradeExpandMode.UNLIMITED) { + val headerAdapterPosition = holder.bindingAdapterPosition + val isHeaderExpanded = expandedPositions[headerPosition] + + expandedPositions.flip(headerPosition) + + if (!isHeaderExpanded) { + val updatedList = items.toMutableList() + .apply { addAll(headerAdapterPosition + 1, header.grades) } + + refreshList(updatedList) + scrollToHeaderWithSubItems(headerAdapterPosition, header.grades.size) + } else { + val startPosition = headerAdapterPosition + 1 + val updatedList = items.toMutableList() + .apply { + subList(startPosition, startPosition + header.grades.size).clear() + } + + refreshList(updatedList) + } + } + } + + @SuppressLint("SetTextI18n") + private fun bindItemViewHolder(holder: ItemViewHolder, grade: Grade) { + val context = holder.binding.root.context + + with(holder.binding) { + gradeItemValue.run { + text = grade.entry + backgroundTintList = ColorStateList.valueOf( + context.getCompatColor(grade.getBackgroundColor(gradeColorTheme)) + ) + } + gradeItemDescription.text = when { + grade.description.isNotBlank() -> grade.description + grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol + else -> context.getString(R.string.all_no_description) + } + gradeItemDate.text = grade.date.toFormattedString() + gradeItemWeight.text = "${context.getString(R.string.grade_weight)}: ${grade.weight}" + gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE + + root.setOnClickListener { + holder.bindingAdapterPosition.let { + if (it != NO_POSITION) onClickListener(grade, it) + } + } + } + } + + private fun formatAverage(average: Double?, resources: Resources) = + if (average == null || average == .0) { + resources.getString(R.string.grade_no_average) + } else { + resources.getString(R.string.grade_average, average) + } + + private fun formatAverageAllYear(average: Double?, resources: Resources) = + if (average == null || average == .0) { + resources.getString(R.string.grade_no_average) + } else { + resources.getString(R.string.grade_average_year, average) + } + + private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemGradeDetailsBinding) : + RecyclerView.ViewHolder(binding.root) + + private class GradeDetailsDiffUtil( + private val old: List, + private val new: List + ) : DiffUtil.Callback() { + + override fun getOldListSize() = old.size + + override fun getNewListSize() = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return old[oldItemPosition] == new[newItemPosition] + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index a8f8a865..39f72f8b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -1,90 +1,96 @@ package io.github.wulkanowy.ui.modules.grade.details +import android.app.Dialog +import android.content.res.ColorStateList import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.utils.colorStringId -import io.github.wulkanowy.utils.getBackgroundColor -import io.github.wulkanowy.utils.getGradeColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_grade.* +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.databinding.DialogGradeBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.* -class GradeDetailsDialog : DialogFragment() { +@AndroidEntryPoint +class GradeDetailsDialog : BaseDialogFragment() { private lateinit var grade: Grade - private lateinit var colorScheme: String + private lateinit var gradeColorTheme: GradeColorTheme companion object { - private const val ARGUMENT_KEY = "Item" - private const val COLOR_SCHEME_KEY = "Scheme" - fun newInstance(grade: Grade, colorScheme: String): GradeDetailsDialog { - return GradeDetailsDialog().apply { - arguments = Bundle().apply { - putSerializable(ARGUMENT_KEY, grade) - putString(COLOR_SCHEME_KEY, colorScheme) - } - } + private const val ARGUMENT_KEY = "Item" + + private const val COLOR_THEME_KEY = "Theme" + + fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply { + arguments = bundleOf( + ARGUMENT_KEY to grade, + COLOR_THEME_KEY to colorTheme + ) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - grade = getSerializable(ARGUMENT_KEY) as Grade - colorScheme = getString(COLOR_SCHEME_KEY) ?: "default" - } + grade = requireArguments().serializable(ARGUMENT_KEY) + gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_grade, container, false) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogGradeBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - gradeDialogSubject.text = grade.subject + with(binding) { + gradeDialogSubject.text = grade.subject - gradeDialogColorAndWeightValue.run { - text = context.getString(R.string.grade_weight_value, grade.weight) - setBackgroundResource(grade.getGradeColor()) - } + gradeDialogWeightValue.text = grade.weight + gradeDialogWeightLayout.backgroundTintList = + ColorStateList.valueOf(requireContext().getCompatColor(grade.getGradeColor())) - gradeDialogDateValue.text = grade.date.toFormattedString() - gradeDialogColorValue.text = getString(grade.colorStringId) + gradeDialogDateValue.text = grade.date.toFormattedString() + gradeDialogColorValue.text = getString(grade.colorStringId) - gradeDialogCommentValue.apply { - if (grade.comment.isBlank()) { - visibility = GONE - gradeDialogComment.visibility = GONE - } else text = grade.comment - } - - gradeDialogValue.run { - text = grade.entry - setBackgroundResource(grade.getBackgroundColor(colorScheme)) - } - - gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { - getString(R.string.all_no_data) - } else grade.teacher - - gradeDialogDescriptionValue.text = grade.run { - when { - description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol - description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description) - gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description" - else -> description + gradeDialogCommentValue.apply { + if (grade.comment.isBlank()) { + visibility = GONE + gradeDialogComment.visibility = GONE + } else text = grade.comment } - } - gradeDialogClose.setOnClickListener { dismiss() } + gradeDialogValue.run { + text = grade.entry + backgroundTintList = ColorStateList.valueOf( + ContextCompat.getColor( + requireContext(), + grade.getBackgroundColor(gradeColorTheme) + ) + ) + } + + gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) } + + gradeDialogDescriptionValue.text = grade.run { + when { + description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol + description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description) + gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description" + else -> description + } + } + + gradeDialogClose.setOnClickListener { dismiss() } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt index 9505d354..23d767a6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt @@ -1,37 +1,35 @@ package io.github.wulkanowy.ui.modules.grade.details import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IExpandable -import eu.davidea.flexibleadapter.items.IFlexible +import android.view.View.* +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.enums.GradeExpandMode +import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_grade_details.* +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeChildView { +@AndroidEntryPoint +class GradeDetailsFragment : + BaseFragment(R.layout.fragment_grade_details), GradeDetailsView, + GradeView.GradeChildView { @Inject lateinit var presenter: GradeDetailsPresenter @Inject - lateinit var gradeDetailsAdapter: FlexibleAdapter> + lateinit var gradeDetailsAdapter: GradeDetailsAdapter private var gradeDetailsMenu: Menu? = null @@ -39,36 +37,19 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh fun newInstance() = GradeDetailsFragment() } - override val emptyAverageString: String - get() = getString(R.string.grade_no_average) - - override val averageString: String - get() = getString(R.string.grade_average) - - override val pointsSumString: String - get() = getString(R.string.grade_points_sum) - - override val weightString: String - get() = getString(R.string.grade_weight) - - override val noDescriptionString: String - get() = getString(R.string.all_no_description) - override val isViewEmpty - get() = gradeDetailsAdapter.isEmpty + get() = gradeDetailsAdapter.itemCount == 0 + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_grade_details, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = gradeDetailsRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeDetailsBinding.bind(view) + messageContainer = binding.gradeDetailsRecycler presenter.onAttachView(this) } @@ -79,22 +60,19 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun initView() { - gradeDetailsAdapter.run { - isAutoCollapseOnExpand = true - isAutoScrollOnExpand = true - setOnItemClickListener { presenter.onGradeItemSelected(it) } - } + gradeDetailsAdapter.onClickListener = presenter::onGradeItemSelected - gradeDetailsRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) - adapter = gradeDetailsAdapter - addItemDecoration(GradeDetailsHeaderItemDecoration(context) - .withDefaultDivider(R.layout.header_grade_details) - ) + with(binding) { + with(gradeDetailsRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = gradeDetailsAdapter + } + gradeDetailsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + gradeDetailsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + gradeDetailsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } + gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } - gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -102,16 +80,23 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh else false } - override fun updateData(data: List) { - gradeDetailsAdapter.updateDataSet(data, true) + override fun updateData(data: List, expandMode: GradeExpandMode, gradeColorTheme: GradeColorTheme) { + with(gradeDetailsAdapter) { + this.gradeColorTheme = gradeColorTheme + setDataItems(data, expandMode) + notifyDataSetChanged() + } } - override fun updateItem(item: AbstractFlexibleItem<*>) { - gradeDetailsAdapter.updateItem(item) + override fun updateItem(item: Grade, position: Int) { + gradeDetailsAdapter.updateDetailsItem(position, item) } override fun clearView() { - gradeDetailsAdapter.clear() + with(gradeDetailsAdapter) { + setDataItems(mutableListOf()) + notifyDataSetChanged() + } } override fun collapseAllItems() { @@ -119,47 +104,47 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun scrollToStart() { - gradeDetailsRecycler.scrollToPosition(0) + binding.gradeDetailsRecycler.smoothScrollToPosition(0) } - override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? { - return gradeDetailsAdapter.getExpandableOf(item) + override fun getHeaderOfItem(subject: String): GradeDetailsItem { + return gradeDetailsAdapter.getHeaderItem(subject) } - override fun getGradeNumberString(number: Int): String { - return resources.getQuantityString(R.plurals.grade_number_item, number, number) + override fun updateHeaderItem(item: GradeDetailsItem) { + gradeDetailsAdapter.updateHeaderItem(item) } override fun showProgress(show: Boolean) { - gradeDetailsProgress.visibility = if (show) VISIBLE else GONE + binding.gradeDetailsProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - gradeDetailsSwipe.isEnabled = enable + binding.gradeDetailsSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeDetailsError.visibility = if (show) VISIBLE else GONE + binding.gradeDetailsError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - gradeDetailsErrorMessage.text = message + binding.gradeDetailsErrorMessage.text = message } override fun showRefresh(show: Boolean) { - gradeDetailsSwipe.isRefreshing = show + binding.gradeDetailsSwipe.isRefreshing = show } - override fun showGradeDialog(grade: Grade, colorScheme: String) { - (activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade, colorScheme)) + override fun showGradeDialog(grade: Grade, colorTheme: GradeColorTheme) { + (activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade, colorTheme)) } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { @@ -187,7 +172,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt deleted file mode 100644 index 4a34a145..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeader.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade.details - -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractExpandableItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.ExpandableViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_grade_details.* - -class GradeDetailsHeader( - private val subject: String, - private val number: String, - private val average: String, - private val pointsSum: String, - var newGrades: Int, - private val isExpandable: Boolean -) : AbstractExpandableItem() { - - init { - isExpanded = !isExpandable - } - - override fun getLayoutRes() = R.layout.header_grade_details - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.run { - gradeHeaderSubject.apply { - text = subject - maxLines = if (isExpanded) 2 else 1 - } - gradeHeaderAverage.text = average - gradeHeaderPointsSum.text = pointsSum - gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE - gradeHeaderNumber.text = number - gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE - if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10) - gradeHeaderContainer.isEnabled = isExpandable - - isViewExpandable = isExpandable - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradeDetailsHeader - - if (subject != other.subject) return false - if (number != other.number) return false - if (average != other.average) return false - if (isExpandable != other.isExpandable) return false - - return true - } - - override fun hashCode(): Int { - var result = subject.hashCode() - result = 31 * result + number.hashCode() - result = 31 * result + average.hashCode() - result = 31 * result + isExpandable.hashCode() - return result - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : - ExpandableViewHolder(view, adapter), LayoutContainer { - - var isViewExpandable = true - - init { - contentView.setOnClickListener(this) - } - - override val containerView: View - get() = contentView - - override fun isViewCollapsibleOnClick() = isViewExpandable - - override fun isViewExpandableOnClick() = isViewExpandable - - override fun onClick(view: View?) { - super.onClick(view) - mAdapter.getItem(adapterPosition)?.let { mAdapter.updateItem(it) } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt deleted file mode 100644 index 39a911e6..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsHeaderItemDecoration.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade.details - -import android.content.Context -import android.graphics.Canvas -import androidx.recyclerview.widget.RecyclerView -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration - -class GradeDetailsHeaderItemDecoration(context: Context) : FlexibleItemDecoration(context) { - - override fun drawVertical(canvas: Canvas, parent: RecyclerView) { - canvas.save() - val left: Int - val right: Int - if (parent.clipToPadding) { - left = parent.paddingLeft - right = parent.width - parent.paddingRight - canvas.clipRect(left, parent.paddingTop, right, - parent.height - parent.paddingBottom) - } else { - left = 0 - right = parent.width - } - - val itemCount = parent.childCount - for (i in 1 until itemCount) { - val child = parent.getChildAt(i) - val viewHolder = parent.getChildViewHolder(child) - if (shouldDrawDivider(viewHolder)) { - parent.getDecoratedBoundsWithMargins(child, mBounds) - val bottom = mBounds.top + Math.round(child.translationY) - val top = bottom - mDivider.intrinsicHeight - mDivider.setBounds(left, top, right, bottom) - mDivider.draw(canvas) - } - } - canvas.restore() - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt index 1e47eca5..1224efaf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsItem.kt @@ -1,74 +1,21 @@ package io.github.wulkanowy.ui.modules.grade.details -import android.annotation.SuppressLint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_grade_details.* - -class GradeDetailsItem( - val grade: Grade, - private val valueBgColor: Int, - private val weightString: String, - private val noDescriptionString: String -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_grade_details - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.run { - gradeItemValue.run { - text = grade.entry - setBackgroundResource(valueBgColor) - } - gradeItemDescription.text = when { - grade.description.isNotBlank() -> grade.description - grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol - else -> noDescriptionString - } - gradeItemDate.text = grade.date.toFormattedString() - gradeItemWeight.text = "$weightString: ${grade.weight}" - gradeItemNote.visibility = if (!grade.isRead) VISIBLE else GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradeDetailsItem - - if (grade != other.grade) return false - if (grade.id != other.grade.id) return false - if (weightString != other.weightString) return false - if (valueBgColor != other.valueBgColor) return false - - return true - } - - override fun hashCode(): Int { - var result = grade.hashCode() - result = 31 * result + grade.id.toInt() - result = 31 * result + weightString.hashCode() - result = 31 * result + valueBgColor - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } +enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) +} + +data class GradeDetailsItem( + val value: Any, + val viewType: ViewType +) + +data class GradeDetailsHeader( + val subject: String, + val average: Double?, + val averageAllYear: Double?, + val pointsSum: String?, + val grades: List +) { + var newGrades = 0 } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index a9e5b2b7..ec5d34c5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,30 +1,41 @@ package io.github.wulkanowy.ui.modules.grade.details -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.enums.GradeExpandMode +import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC +import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE +import io.github.wulkanowy.data.enums.GradeSortingMode.DATE +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.ui.modules.grade.GradeSubject +import io.github.wulkanowy.utils.AnalyticsHelper +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.first import timber.log.Timber import javax.inject.Inject class GradeDetailsPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val gradeRepository: GradeRepository, private val semesterRepository: SemesterRepository, private val preferencesRepository: PreferencesRepository, private val averageProvider: GradeAverageProvider, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var newGradesAmount: Int = 0 @@ -40,50 +51,43 @@ class GradeDetailsPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { currentSemesterId = semesterId + + if (!forceRefresh) view?.showErrorView(false) loadData(semesterId, forceRefresh) } - fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is GradeDetailsItem) { - Timber.i("Select grade item ${item.grade.id}") - view?.apply { - showGradeDialog(item.grade, preferencesRepository.gradeColorTheme) - if (!item.grade.isRead) { - item.grade.isRead = true - updateItem(item) - getHeaderOfItem(item)?.let { header -> - if (header is GradeDetailsHeader) { - header.newGrades-- - updateItem(header) - } - } - newGradesAmount-- - updateMarkAsDoneButton() - updateGrade(item.grade) + fun onGradeItemSelected(grade: Grade, position: Int) { + Timber.i("Select grade item ${grade.id}, position: $position") + view?.apply { + showGradeDialog(grade, preferencesRepository.gradeColorTheme) + if (!grade.isRead) { + grade.isRead = true + updateItem(grade, position) + getHeaderOfItem(grade.subject).let { header -> + (header.value as GradeDetailsHeader).newGrades-- + updateHeaderItem(header) } + newGradesAmount-- + updateMarkAsDoneButton() + updateGrade(grade) } } } fun onMarkAsReadSelected(): Boolean { - Timber.i("Select mark grades as read") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it) } - .flatMap { gradeRepository.getUnreadGrades(it.first { item -> item.semesterId == currentSemesterId }) } - .map { it.map { grade -> grade.apply { isRead = true } } } - .flatMapCompletable { - Timber.i("Mark as read ${it.size} grades") - gradeRepository.updateGrades(it) - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Mark as read result: Success") - loadData(currentSemesterId, false) - }, { - Timber.i("Mark as read result: An exception occurred") - errorHandler.dispatch(it) - })) + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semesters = semesterRepository.getSemesters(student) + val semester = semesters.first { item -> item.semesterId == currentSemesterId } + val unreadGrades = gradeRepository.getUnreadGrades(semester).first() + + Timber.i("Mark as read ${unreadGrades.size} grades") + gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } }) + } + .logResourceStatus("mark grades as read") + .onResourceSuccess { loadData(currentSemesterId, false) } + .onResourceError(errorHandler::dispatch) + .launch("mark") return true } @@ -107,7 +111,7 @@ class GradeDetailsPresenter @Inject constructor( fun onParentViewReselected() { view?.run { if (!isViewEmpty) { - if (preferencesRepository.isGradeExpandable) collapseAllItems() + if (preferencesRepository.gradeExpandMode != GradeExpandMode.ALWAYS_EXPANDED) collapseAllItems() scrollToStart() } } @@ -122,7 +126,7 @@ class GradeDetailsPresenter @Inject constructor( showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } fun updateMarkAsDoneButton() { @@ -130,43 +134,56 @@ class GradeDetailsPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade details data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } } - .flatMap { (student, semesters) -> - averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) - .flatMap { averages -> - gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh) - .map { it.sortedByDescending { grade -> grade.date } } - .map { it.groupBy { grade -> grade.subject }.toSortedMap() } - .map { createGradeItems(it, averages) } - } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) + } + .logResourceStatus("load grade details") + .onResourceData { + val gradeItems = createGradeItems(it) view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(gradeItems.isNotEmpty()) + showEmpty(gradeItems.isEmpty()) + updateNewGradesAmount(it) + updateMarkAsDoneButton() + updateData( + data = gradeItems, + expandMode = preferencesRepository.gradeExpandMode, + preferencesRepository.gradeColorTheme + ) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_details", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) showRefresh(false) showProgress(false) - enableSwipe(true) notifyParentDataLoaded(semesterId) } } - .subscribe({ - Timber.i("Loading grade details result: Success") - newGradesAmount = it.sumBy { gradeDetailsHeader -> gradeDetailsHeader.newGrades } - updateMarkAsDoneButton() - view?.run { - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - updateData(it) - } - analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading grade details result: An exception occurred") + .catch { errorHandler.dispatch(it) - }) + view?.notifyParentDataLoaded(semesterId) + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun updateNewGradesAmount(grades: List) { + newGradesAmount = grades.sumOf { item -> + item.grades.sumOf { grade -> (if (!grade.isRead) 1 else 0).toInt() } + } } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -176,55 +193,60 @@ class GradeDetailsPresenter @Inject constructor( setErrorDetails(message) showErrorView(true) showEmpty(false) + showProgress(false) } else showError(message, error) } } - private fun createGradeItems(items: Map>, averages: List>): List { - val isGradeExpandable = preferencesRepository.isGradeExpandable - val gradeColorTheme = preferencesRepository.gradeColorTheme - - val noDescriptionString = view?.noDescriptionString.orEmpty() - val weightString = view?.weightString.orEmpty() - val pointsSumString = view?.pointsSumString.orEmpty() - - return items.map { subject -> - GradeDetailsHeader( - subject = subject.key, - average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second), - pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(), - number = view?.getGradeNumberString(subject.value.size).orEmpty(), - newGrades = subject.value.filter { grade -> !grade.isRead }.size, - isExpandable = isGradeExpandable - ).apply { - subItems = subject.value.map { item -> - GradeDetailsItem( - grade = item, - valueBgColor = item.getBackgroundColor(gradeColorTheme), - weightString = weightString, - noDescriptionString = noDescriptionString - ) + private fun createGradeItems(items: List): List { + return items + .let { gradesWithAverages -> + if (!preferencesRepository.showSubjectsWithoutGrades) { + gradesWithAverages.filter { it.grades.isNotEmpty() } + } else gradesWithAverages + } + .let { gradeSubjects -> + when (preferencesRepository.gradeSortingMode) { + DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage -> + gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date + } + ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage -> + gradeDetailsWithAverage.subject.lowercase() + } + AVERAGE -> gradeSubjects.sortedByDescending { it.average } } } - } - } + .map { gradeSubject -> + val subItems = gradeSubject.grades + .sortedByDescending { it.date } + .map { GradeDetailsItem(it, ViewType.ITEM) } - private fun formatAverage(average: Double?): String { - return view?.run { - if (average == null || average == .0) emptyAverageString - else averageString.format(average) - }.orEmpty() + val gradeDetailsItems = listOf( + GradeDetailsItem( + GradeDetailsHeader( + subject = gradeSubject.subject, + average = gradeSubject.average, + averageAllYear = gradeSubject.summary.averageAllYear, + pointsSum = gradeSubject.points, + grades = subItems, + ).apply { + newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size + }, ViewType.HEADER + ) + ) + + if (preferencesRepository.gradeExpandMode == GradeExpandMode.ALWAYS_EXPANDED) { + gradeDetailsItems + subItems + } else { + gradeDetailsItems + } + }.flatten() } private fun updateGrade(grade: Grade) { - Timber.i("Attempt to update grade ${grade.id}") - disposable.add(gradeRepository.updateGrade(grade) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ Timber.i("Update grade result: Success") }) - { error -> - Timber.i("Update grade result: An exception occurred") - errorHandler.dispatch(error) - }) + resourceFlow { gradeRepository.updateGrade(grade) } + .logResourceStatus("update grade result ${grade.id}") + .onResourceError(errorHandler::dispatch) + .launch("update") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt index e2977bcb..491bf300 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt @@ -1,30 +1,21 @@ package io.github.wulkanowy.ui.modules.grade.details -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IExpandable -import eu.davidea.flexibleadapter.items.IFlexible import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.ui.base.BaseView interface GradeDetailsView : BaseView { val isViewEmpty: Boolean - val emptyAverageString: String - - val averageString: String - - val pointsSumString: String - - val weightString: String - - val noDescriptionString: String - fun initView() - fun updateData(data: List) + fun updateData(data: List, expandMode: GradeExpandMode, gradeColorTheme: GradeColorTheme) - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateItem(item: Grade, position: Int) + + fun updateHeaderItem(item: GradeDetailsItem) fun clearView() @@ -32,7 +23,7 @@ interface GradeDetailsView : BaseView { fun collapseAllItems() - fun showGradeDialog(grade: Grade, colorScheme: String) + fun showGradeDialog(grade: Grade, colorTheme: GradeColorTheme) fun showContent(show: Boolean) @@ -54,7 +45,5 @@ interface GradeDetailsView : BaseView { fun enableMarkAsDoneButton(enable: Boolean) - fun getGradeNumberString(number: Int): String - - fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? + fun getHeaderOfItem(subject: String): GradeDetailsItem } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index a7b7b653..e5f1eba0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics import android.graphics.Color import android.view.LayoutInflater -import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup @@ -10,28 +9,34 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.LegendEntry -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry -import com.github.mikephil.charting.data.PieData -import com.github.mikephil.charting.data.PieDataSet -import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.data.* import com.github.mikephil.charting.formatter.ValueFormatter import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding +import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding +import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.synthetic.main.item_grade_statistics_bar.view.* -import kotlinx.android.synthetic.main.item_grade_statistics_pie.view.* import javax.inject.Inject +import kotlin.math.max +import kotlin.math.roundToInt class GradeStatisticsAdapter @Inject constructor() : RecyclerView.Adapter() { + var currentDataType = GradeStatisticsItem.DataType.PARTIAL + var items = emptyList() - var theme: String = "vulcan" + lateinit var gradeColorTheme: GradeColorTheme + + var showAllSubjectsOnList: Boolean = false + + var onDataTypeChangeListener: () -> Unit = {} private val vulcanGradeColors = listOf( 6 to R.color.grade_vulcan_six, @@ -60,61 +65,161 @@ class GradeStatisticsAdapter @Inject constructor() : "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" ) - override fun getItemCount() = items.size + override fun getItemCount() = + (if (showAllSubjectsOnList) items.size else (if (items.isEmpty()) 0 else 1)) + 1 - override fun getItemViewType(position: Int): Int { - return when (items[position].type) { - ViewType.SEMESTER, ViewType.PARTIAL -> R.layout.item_grade_statistics_pie - ViewType.POINTS -> R.layout.item_grade_statistics_bar + override fun getItemViewType(position: Int) = + if (position == 0) { + ViewType.HEADER.id + } else { + when (items[position - 1].type) { + GradeStatisticsItem.DataType.PARTIAL -> ViewType.PARTIAL.id + GradeStatisticsItem.DataType.POINTS -> ViewType.POINTS.id + GradeStatisticsItem.DataType.SEMESTER -> ViewType.SEMESTER.id + } } - } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val viewHolder = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + val inflater = LayoutInflater.from(parent.context) return when (viewType) { - R.layout.item_grade_statistics_bar -> GradeStatisticsBar(viewHolder) - else -> GradeStatisticsPie(viewHolder) + ViewType.PARTIAL.id -> PartialViewHolder( + ItemGradeStatisticsPieBinding.inflate(inflater, parent, false) + ) + ViewType.SEMESTER.id -> SemesterViewHolder( + ItemGradeStatisticsPieBinding.inflate(inflater, parent, false) + ) + ViewType.POINTS.id -> PointsViewHolder( + ItemGradeStatisticsBarBinding.inflate(inflater, parent, false) + ) + ViewType.HEADER.id -> HeaderViewHolder( + ItemGradeStatisticsHeaderBinding.inflate(inflater, parent, false) + ) + else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val index = position - 1 + when (holder) { - is GradeStatisticsPie -> bindPieChart(holder, items[position].partial) - is GradeStatisticsBar -> bindBarChart(holder, items[position].points!!) + is PartialViewHolder -> bindPartialChart(holder.binding, items[index].partial!!) + is SemesterViewHolder -> bindSemesterChart(holder.binding, items[index].semester!!) + is PointsViewHolder -> bindBarChart(holder.binding, items[index].points!!) + is HeaderViewHolder -> bindHeader(holder.binding) } } - private fun bindPieChart(holder: GradeStatisticsPie, partials: List) { - with(holder.view.gradeStatisticsPieTitle) { - text = partials.firstOrNull()?.subject - visibility = if (items.size == 1) GONE else VISIBLE + private fun bindHeader(binding: ItemGradeStatisticsHeaderBinding) { + binding.gradeStatisticsTypeSwitch.check( + when (currentDataType) { + GradeStatisticsItem.DataType.PARTIAL -> R.id.gradeStatisticsTypePartial + GradeStatisticsItem.DataType.SEMESTER -> R.id.gradeStatisticsTypeSemester + GradeStatisticsItem.DataType.POINTS -> R.id.gradeStatisticsTypePoints + } + ) + + binding.gradeStatisticsTypeSwitch.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked) return@addOnButtonCheckedListener + + currentDataType = when (checkedId) { + R.id.gradeStatisticsTypePartial -> GradeStatisticsItem.DataType.PARTIAL + R.id.gradeStatisticsTypeSemester -> GradeStatisticsItem.DataType.SEMESTER + R.id.gradeStatisticsTypePoints -> GradeStatisticsItem.DataType.POINTS + else -> GradeStatisticsItem.DataType.PARTIAL + } + onDataTypeChangeListener() + } + } + + private fun bindPartialChart( + binding: ItemGradeStatisticsPieBinding, + partials: GradePartialStatistics + ) { + val studentAverage = partials.studentAverage.takeIf { it.isNotEmpty() }?.let { + binding.root.context.getString(R.string.grade_statistics_student_average, it) + } + bindPieChart( + binding = binding, + subject = partials.subject, + average = partials.classAverage, + studentValue = studentAverage, + amounts = partials.classAmounts + ) + } + + private fun bindSemesterChart( + binding: ItemGradeStatisticsPieBinding, + semester: GradeSemesterStatistics + ) { + val studentAverage = semester.studentAverage.takeIf { it.isNotBlank() } + val studentGrade = semester.studentGrade.takeIf { it != 0 } + + val studentValue = when { + studentAverage != null -> binding.root.context.getString( + R.string.grade_statistics_student_average, + studentAverage + ) + studentGrade != null -> binding.root.context.getString( + R.string.grade_statistics_student_grade, + studentGrade.toString() + ) + else -> null + } + bindPieChart( + binding = binding, + subject = semester.subject, + average = semester.classAverage, + studentValue = studentValue, + amounts = semester.amounts + ) + } + + private fun bindPieChart( + binding: ItemGradeStatisticsPieBinding, + subject: String, + average: String, + studentValue: String?, + amounts: List + ) { + with(binding.gradeStatisticsPieTitle) { + text = subject + visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE } - val gradeColors = when (theme) { - "vulcan" -> vulcanGradeColors + val gradeColors = when (gradeColorTheme) { + GradeColorTheme.VULCAN -> vulcanGradeColors else -> materialGradeColors } - val dataset = PieDataSet(partials.map { - PieEntry(it.amount.toFloat(), it.grade.toString()) - }, "Legenda") + val dataset = PieDataSet( + amounts.mapIndexed { grade, amount -> + PieEntry(amount.toFloat(), (grade + 1).toString()) + }.reversed().filterNot { it.value == 0f }, + binding.root.context.getString(R.string.grade_statistics_legend) + ) with(dataset) { valueTextSize = 12f sliceSpace = 1f valueTextColor = Color.WHITE - setColors(partials.map { - gradeColors.single { color -> color.first == it.grade }.second - }.toIntArray(), holder.view.context) + val grades = amounts.mapIndexed { grade, amount -> (grade + 1) to amount } + .filterNot { it.second == 0 } + setColors(grades.reversed().map { (grade, _) -> + gradeColors.single { color -> color.first == grade }.second + }.toIntArray(), binding.root.context) } - with(holder.view.gradeStatisticsPie) { + with(binding.gradeStatisticsPie) { setTouchEnabled(false) - if (partials.size == 1) animateXY(1000, 1000) + if (amounts.size == 1) animateXY(1000, 1000) data = PieData(dataset).apply { setValueFormatter(object : ValueFormatter() { override fun getPieLabel(value: Float, pieEntry: PieEntry): String { - return resources.getQuantityString(R.plurals.grade_number_item, value.toInt(), value.toInt()) + return resources.getQuantityString( + R.plurals.grade_number_item, + value.toInt(), + value.toInt() + ) } }) } @@ -129,10 +234,16 @@ class GradeStatisticsAdapter @Inject constructor() : }) } + val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it } + .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } + val averageString = + binding.root.context.getString(R.string.grade_statistics_class_average, average) + minAngleForSlices = 25f description.isEnabled = false - centerText = partials.fold(0) { acc, it -> acc + it.amount } - .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } + centerText = + numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() } + .orEmpty() + studentValue?.let { "\n$it" }.orEmpty() setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) @@ -140,27 +251,32 @@ class GradeStatisticsAdapter @Inject constructor() : } } - private fun bindBarChart(holder: GradeStatisticsBar, points: GradePointsStatistics) { - with(holder.view.gradeStatisticsBarTitle) { + private fun bindBarChart( + binding: ItemGradeStatisticsBarBinding, + points: GradePointsStatistics + ) { + with(binding.gradeStatisticsBarTitle) { text = points.subject visibility = if (items.size == 1) GONE else VISIBLE } - val dataset = BarDataSet(listOf( - BarEntry(1f, points.others.toFloat()), - BarEntry(2f, points.student.toFloat()) - ), "Legenda") + val dataset = BarDataSet( + listOf( + BarEntry(1f, points.others.toFloat()), + BarEntry(2f, points.student.toFloat()) + ), binding.root.context.getString(R.string.grade_statistics_legend) + ) with(dataset) { valueTextSize = 12f - valueTextColor = holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary) + valueTextColor = binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary) valueFormatter = object : ValueFormatter() { - override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" + override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}" } colors = gradePointsColors } - with(holder.view.gradeStatisticsBar) { + with(binding.gradeStatisticsBar) { setTouchEnabled(false) if (items.size == 1) animateXY(1000, 1000) data = BarData(dataset).apply { @@ -169,12 +285,13 @@ class GradeStatisticsAdapter @Inject constructor() : } legend.setCustom(listOf( LegendEntry().apply { - label = "Średnia klasy" + label = binding.root.context.getString(R.string.grade_statistics_average_class) formColor = gradePointsColors[0] form = Legend.LegendForm.SQUARE }, LegendEntry().apply { - label = "Uczeń" + label = + binding.root.context.getString(R.string.grade_statistics_average_student) formColor = gradePointsColors[1] form = Legend.LegendForm.SQUARE } @@ -183,27 +300,40 @@ class GradeStatisticsAdapter @Inject constructor() : description.isEnabled = false - holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary).let { + binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let { axisLeft.textColor = it axisRight.textColor = it } xAxis.setDrawLabels(false) xAxis.setDrawGridLines(false) + + val yMaxFromValues = (max(points.others, points.student)).roundToInt() + 30f + val yMaxFromValuesWithMargin = ((yMaxFromValues / 10.0).roundToInt() * 10).toFloat() + val yMax = yMaxFromValuesWithMargin.coerceAtLeast(100f) + val yLabelCount = (yMax / 10).toInt() + 1 with(axisLeft) { axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 + axisMaximum = yMax + labelCount = yLabelCount } with(axisRight) { axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 + axisMaximum = yMax + labelCount = yLabelCount } invalidate() } } - class GradeStatisticsPie(val view: View) : RecyclerView.ViewHolder(view) + private class PartialViewHolder(val binding: ItemGradeStatisticsPieBinding) : + RecyclerView.ViewHolder(binding.root) - class GradeStatisticsBar(val view: View) : RecyclerView.ViewHolder(view) + private class SemesterViewHolder(val binding: ItemGradeStatisticsPieBinding) : + RecyclerView.ViewHolder(binding.root) + + private class PointsViewHolder(val binding: ItemGradeStatisticsBarBinding) : + RecyclerView.ViewHolder(binding.root) + + private class HeaderViewHolder(val binding: ItemGradeStatisticsHeaderBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index c05cf380..edc384c5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -1,23 +1,28 @@ package io.github.wulkanowy.ui.modules.grade.statistics import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.databinding.FragmentGradeStatisticsBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.setOnItemSelectedListener -import kotlinx.android.synthetic.main.fragment_grade_statistics.* import javax.inject.Inject -class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.GradeChildView { +@AndroidEntryPoint +class GradeStatisticsFragment : + BaseFragment(R.layout.fragment_grade_statistics), + GradeStatisticsView, GradeView.GradeChildView { @Inject lateinit var presenter: GradeStatisticsPresenter @@ -29,51 +34,59 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G companion object { private const val SAVED_CHART_TYPE = "CURRENT_TYPE" + private const val SAVED_SUBJECT_NAME = "SUBJECT_NAME" fun newInstance() = GradeStatisticsFragment() } override val isViewEmpty get() = statisticsAdapter.items.isEmpty() - override val currentType - get() = when (gradeStatisticsTypeSwitch.checkedRadioButtonId) { - R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER - R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL - else -> ViewType.POINTS - } + override val currentType get() = statisticsAdapter.currentDataType - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_grade_statistics, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = gradeStatisticsSwipe - presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeStatisticsBinding.bind(view) + messageContainer = binding.gradeStatisticsRecycler + presenter.onAttachView( + view = this, + type = savedInstanceState?.serializable(SAVED_CHART_TYPE), + subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME), + ) } override fun initView() { - with(gradeStatisticsRecycler) { + statisticsAdapter.onDataTypeChangeListener = presenter::onTypeChange + + with(binding.gradeStatisticsRecycler) { layoutManager = LinearLayoutManager(requireContext()) + statisticsAdapter.currentDataType = presenter.currentType adapter = statisticsAdapter } - subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) + subjectsAdapter = + ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) - with(gradeStatisticsSubjects) { + with(binding.gradeStatisticsSubjects) { adapter = subjectsAdapter setOnItemSelectedListener { presenter.onSubjectSelected(it?.text?.toString()) } } - gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) + with(binding) { + gradeStatisticsSubjectsContainer.elevation = requireContext().dpToPx(1f) - gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() } - gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + gradeStatisticsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + gradeStatisticsSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() } + gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } - override fun updateSubjects(data: ArrayList) { + override fun updateSubjects(data: List, selectedIndex: Int) { + binding.gradeStatisticsSubjects.setSelection(selectedIndex) with(subjectsAdapter) { clear() addAll(data) @@ -81,15 +94,21 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } } - override fun updateData(items: List, theme: String) { - statisticsAdapter.theme = theme - statisticsAdapter.items = items - statisticsAdapter.notifyDataSetChanged() + override fun updateData( + newItems: List, + newTheme: GradeColorTheme, + showAllSubjectsOnStatisticsList: Boolean + ) { + with(statisticsAdapter) { + showAllSubjectsOnList = showAllSubjectsOnStatisticsList + gradeColorTheme = newTheme + items = newItems + notifyDataSetChanged() + } } override fun showSubjects(show: Boolean) { - gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE - gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE + binding.gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.GONE } override fun clearView() { @@ -97,35 +116,31 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun resetView() { - gradeStatisticsScroll.scrollTo(0, 0) - } - - override fun showContent(show: Boolean) { - gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsRecycler.scrollToPosition(0) } override fun showEmpty(show: Boolean) { - gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE + binding.gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE } override fun showErrorView(show: Boolean) { - gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE } override fun setErrorDetails(message: String) { - gradeStatisticsErrorMessage.text = message + binding.gradeStatisticsErrorMessage.text = message } override fun showProgress(show: Boolean) { - gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE + binding.gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE } override fun enableSwipe(enable: Boolean) { - gradeStatisticsSwipe.isEnabled = enable + binding.gradeStatisticsSwipe.isEnabled = enable } override fun showRefresh(show: Boolean) { - gradeStatisticsSwipe.isRefreshing = show + binding.gradeStatisticsSwipe.isRefreshing = show } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { @@ -148,18 +163,14 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G (parentFragment as? GradeFragment)?.onChildRefresh() } - override fun onResume() { - super.onResume() - gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() } - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType) + outState.putSerializable(SAVED_SUBJECT_NAME, presenter.currentSubjectName) } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 90c4e38e..aa0e5999 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -1,43 +1,45 @@ package io.github.wulkanowy.ui.modules.grade.statistics +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.subject.SubjectRepository +import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject class GradeStatisticsPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val gradeStatisticsRepository: GradeStatisticsRepository, private val subjectRepository: SubjectRepository, private val semesterRepository: SemesterRepository, private val preferencesRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var subjects = emptyList() private var currentSemesterId = 0 - private var currentSubjectName: String = "Wszystkie" + var currentSubjectName: String = "Wszystkie" + private set private lateinit var lastError: Throwable - var currentType: ViewType = ViewType.PARTIAL + var currentType: GradeStatisticsItem.DataType = GradeStatisticsItem.DataType.PARTIAL private set - fun onAttachView(view: GradeStatisticsView, type: ViewType?) { + fun onAttachView( + view: GradeStatisticsView, + type: GradeStatisticsItem.DataType?, + subjectName: String? + ) { super.onAttachView(view) - currentType = type ?: ViewType.PARTIAL + currentType = type ?: GradeStatisticsItem.DataType.PARTIAL + currentSubjectName = subjectName ?: currentSubjectName view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError } @@ -45,6 +47,7 @@ class GradeStatisticsPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { currentSemesterId = semesterId loadSubjects() + if (!forceRefresh) view?.showErrorView(false) loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh) } @@ -55,16 +58,16 @@ class GradeStatisticsPresenter @Inject constructor( } fun onParentViewChangeSemester() { + clearDataInView() view?.run { showProgress(true) enableSwipe(false) showRefresh(false) - showContent(false) showErrorView(false) showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } fun onSwipeRefresh() { @@ -86,8 +89,8 @@ class GradeStatisticsPresenter @Inject constructor( fun onSubjectSelected(name: String?) { Timber.i("Select grade stats subject $name") + clearDataInView() view?.run { - showContent(false) showProgress(true) enableSwipe(false) showEmpty(false) @@ -100,11 +103,11 @@ class GradeStatisticsPresenter @Inject constructor( } fun onTypeChange() { - val type = view?.currentType ?: ViewType.POINTS + val type = view?.currentType ?: GradeStatisticsItem.DataType.POINTS Timber.i("Select grade stats semester: $type") - disposable.clear() + cancelJobs("load") + clearDataInView() view?.run { - showContent(false) showProgress(true) enableSwipe(false) showEmpty(false) @@ -115,73 +118,138 @@ class GradeStatisticsPresenter @Inject constructor( } private fun loadSubjects() { - Timber.i("Loading grade stats subjects started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - subjectRepository.getSubjects(student, semester) + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + subjectRepository.getSubjects(student, semester) + } + .logResourceStatus("load grade stats subjects") + .onResourceData { + subjects = it + view?.run { + showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) + updateSubjects( + data = it.map { subject -> subject.name }, + selectedIndex = it.indexOfFirst { subject -> + subject.name == currentSubjectName + }, + ) } } - .doOnSuccess { subjects = it } - .map { ArrayList(it.map { subject -> subject.name }) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading grade stats subjects result: Success") - view?.run { - updateSubjects(it) - showSubjects(true) - } - }, { - Timber.i("Loading grade stats subjects result: An exception occurred") - errorHandler.dispatch(it) - }) - ) + .onResourceError(errorHandler::dispatch) + .launch("subjects") } - private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) { - currentSubjectName = subjectName - currentType = type - loadData(semesterId, subjectName, type, forceRefresh) - } - - private fun loadData(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean) { + private fun loadDataByType( + semesterId: Int, + subjectName: String, + type: GradeStatisticsItem.DataType, + forceRefresh: Boolean = false + ) { Timber.i("Loading grade stats data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getSemesters(student).flatMap { semesters -> - val semester = semesters.first { item -> item.semesterId == semesterId } - when (type) { - ViewType.SEMESTER -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, true, forceRefresh) - ViewType.PARTIAL -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, false, forceRefresh) - ViewType.POINTS -> gradeStatisticsRepository.getGradesPointsStatistics(student, semester, subjectName, forceRefresh) + currentType = type + currentSubjectName = when { + preferencesRepository.showAllSubjectsOnStatisticsList -> "Wszystkie" + else -> subjectName + } + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semesters = semesterRepository.getSemesters(student) + val semester = semesters.first { item -> item.semesterId == semesterId } + + with(gradeStatisticsRepository) { + when (type) { + GradeStatisticsItem.DataType.PARTIAL -> { + getGradesPartialStatistics( + student = student, + semester = semester, + subjectName = currentSubjectName, + forceRefresh = forceRefresh + ) + } + GradeStatisticsItem.DataType.SEMESTER -> { + getGradesSemesterStatistics( + student = student, + semester = semester, + subjectName = currentSubjectName, + forceRefresh = forceRefresh + ) + } + GradeStatisticsItem.DataType.POINTS -> { + getGradesPointsStatistics( + student = student, + semester = semester, + subjectName = currentSubjectName, + forceRefresh = forceRefresh + ) } } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + } + .logResourceStatus("load grade stats data") + .mapResourceData { + val isNoContent = checkIsNoContent(it, type) + if (isNoContent) emptyList() else it + } + .onResourceData { view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + updateData( + newItems = it, + newTheme = preferencesRepository.gradeColorTheme, + showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList + ) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_statistics", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) showRefresh(false) showProgress(false) - enableSwipe(true) notifyParentDataLoaded(semesterId) } } - .subscribe({ - Timber.i("Loading grade stats result: Success") - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - showErrorView(false) - updateData(it, preferencesRepository.gradeColorTheme) - } - analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading grade stats result: An exception occurred") - errorHandler.dispatch(it) - }) + .onResourceError(errorHandler::dispatch) + .launch("load") + } + + private fun checkIsNoContent( + items: List, + type: GradeStatisticsItem.DataType + ): Boolean { + return items.isEmpty() || when (type) { + GradeStatisticsItem.DataType.SEMESTER -> { + items.firstOrNull()?.semester?.amounts.orEmpty().sum() == 0 + } + GradeStatisticsItem.DataType.PARTIAL -> { + items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0 + } + GradeStatisticsItem.DataType.POINTS -> { + items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } + ?: false + } + } + } + + private fun clearDataInView() { + view?.updateData( + emptyList(), + preferencesRepository.gradeColorTheme, + preferencesRepository.showAllSubjectsOnStatisticsList + ) } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt index 9ba8524c..4333bb0a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics +import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.ui.base.BaseView @@ -7,13 +8,17 @@ interface GradeStatisticsView : BaseView { val isViewEmpty: Boolean - val currentType: ViewType + val currentType: GradeStatisticsItem.DataType fun initView() - fun updateSubjects(data: ArrayList) + fun updateSubjects(data: List, selectedIndex: Int) - fun updateData(items: List, theme: String) + fun updateData( + newItems: List, + newTheme: GradeColorTheme, + showAllSubjectsOnStatisticsList: Boolean + ) fun showSubjects(show: Boolean) @@ -25,8 +30,6 @@ interface GradeStatisticsView : BaseView { fun resetView() - fun showContent(show: Boolean) - fun showEmpty(show: Boolean) fun showErrorView(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt index 08c37d58..f695eaf9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt @@ -1,7 +1,8 @@ package io.github.wulkanowy.ui.modules.grade.statistics -enum class ViewType { - SEMESTER, - PARTIAL, - POINTS +enum class ViewType(val id: Int) { + SEMESTER(1), + PARTIAL(2), + POINTS(3), + HEADER(4) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt new file mode 100644 index 00000000..1cc74ef0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -0,0 +1,170 @@ +package io.github.wulkanowy.ui.modules.grade.summary + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.ItemGradeSummaryBinding +import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding +import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid +import io.github.wulkanowy.utils.calcFinalAverage +import io.github.wulkanowy.utils.ifNullOrBlank +import java.util.Locale +import javax.inject.Inject + +class GradeSummaryAdapter @Inject constructor( + private val preferencesRepository: PreferencesRepository +) : RecyclerView.Adapter() { + + private enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } + + var items = emptyList() + + var onCalculatedHelpClickListener: () -> Unit = {} + + var onFinalHelpClickListener: () -> Unit = {} + + override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0 + + override fun getItemViewType(position: Int) = when (position) { + 0 -> ViewType.HEADER.id + else -> ViewType.ITEM.id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + ViewType.HEADER.id -> HeaderViewHolder( + ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false) + ) + + ViewType.ITEM.id -> ItemViewHolder( + ItemGradeSummaryBinding.inflate(inflater, parent, false) + ) + + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position - 1]) + } + } + + private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) { + if (items.isEmpty()) return + val gradeSummaries = items + .filter { it.gradeDescriptive == null } + .map { it.gradeSummary } + val isSecondSemester = items.any { item -> + item.gradeSummary.let { it.averageAllYear != null && it.averageAllYear != .0 } + } + + val context = binding.root.context + val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) } + val calculatedSemesterItemsCount = gradeSummaries.count { value -> value.average != 0.0 } + val calculatedAnnualItemsCount = + gradeSummaries.count { value -> value.averageAllYear != 0.0 } + val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) } + val finalAverage = gradeSummaries.calcFinalAverage( + plusModifier = preferencesRepository.gradePlusModifier, + minusModifier = preferencesRepository.gradeMinusModifier, + ) + val calculatedSemesterAverage = gradeSummaries.filter { value -> value.average != 0.0 } + .map { values -> values.average } + .reversed() // fix average precision + .average() + .let { if (it.isNaN()) 0.0 else it } + val calculatedAnnualAverage = gradeSummaries.filter { value -> value.averageAllYear != 0.0 } + .mapNotNull { values -> values.averageAllYear } + .reversed() // fix average precision + .average() + .let { if (it.isNaN()) 0.0 else it } + + with(binding) { + gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedSemesterAverage) + gradeSummaryScrollableHeaderCalculatedAnnual.text = + formatAverage(calculatedAnnualAverage) + gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) + gradeSummaryScrollableHeaderFinalSubjectCount.text = context.getString( + R.string.grade_summary_from_subjects, + finalItemsCount, + allItemsCount + ) + gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString( + R.string.grade_summary_from_subjects, + calculatedSemesterItemsCount, + allItemsCount + ) + gradeSummaryScrollableHeaderCalculatedSubjectCountAnnual.text = context.getString( + R.string.grade_summary_from_subjects, + calculatedAnnualItemsCount, + allItemsCount + ) + gradeSummaryScrollableHeaderCalculatedAnnualContainer.isVisible = isSecondSemester + + gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() } + gradeSummaryCalculatedAverageHelpAnnual.setOnClickListener { onCalculatedHelpClickListener() } + gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() } + } + } + + @SuppressLint("SetTextI18n") + private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) { + val (gradeSummary, gradeDescriptive) = item + + with(binding) { + gradeSummaryItemTitle.text = gradeSummary.subject + gradeSummaryItemPoints.text = gradeSummary.pointsSum + + gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "") + gradeSummaryItemAverageAllYear.text = gradeSummary.averageAllYear?.let { + formatAverage(it, "") + } + + gradeSummaryItemPredicted.text = + "${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim() + gradeSummaryItemFinal.text = + "${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim() + gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank { + root.context.getString(R.string.all_no_data) + } + + gradeSummaryItemAverageContainer.isVisible = gradeSummary.average != .0 + gradeSummaryItemAverageDivider.isVisible = gradeSummaryItemAverageContainer.isVisible + gradeSummaryItemAverageAllYearContainer.isGone = + gradeSummary.averageAllYear == null || gradeSummary.averageAllYear == .0 + gradeSummaryItemAverageAllYearDivider.isGone = + gradeSummaryItemAverageAllYearContainer.isGone + gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null + gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null + gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null + gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank() + gradeSummaryItemPointsDivider.isVisible = gradeSummaryItemPointsContainer.isVisible + } + } + + private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { + return if (average == 0.0) defaultValue + else String.format(Locale.FRANCE, "%.2f", average) + } + + private class HeaderViewHolder(val binding: ScrollableHeaderGradeSummaryBinding) : + RecyclerView.ViewHolder(binding.root) + + private class ItemViewHolder(val binding: ItemGradeSummaryBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index 05fde522..35b2edd5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -1,36 +1,38 @@ package io.github.wulkanowy.ui.modules.grade.summary import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView -import kotlinx.android.synthetic.main.fragment_grade_summary.* +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeChildView { +@AndroidEntryPoint +class GradeSummaryFragment : + BaseFragment(R.layout.fragment_grade_summary), GradeSummaryView, + GradeView.GradeChildView { @Inject lateinit var presenter: GradeSummaryPresenter @Inject - lateinit var gradeSummaryAdapter: FlexibleAdapter> + lateinit var gradeSummaryAdapter: GradeSummaryAdapter companion object { fun newInstance() = GradeSummaryFragment() } override val isViewEmpty - get() = gradeSummaryAdapter.isEmpty + get() = gradeSummaryAdapter.items.isEmpty() override val predictedString get() = getString(R.string.grade_summary_predicted_grade) @@ -38,70 +40,96 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh override val finalString get() = getString(R.string.grade_summary_final_grade) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_grade_summary, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = gradeSummaryRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentGradeSummaryBinding.bind(view) + messageContainer = binding.gradeSummaryRecycler presenter.onAttachView(this) } override fun initView() { - gradeSummaryAdapter.setDisplayHeadersAtStartUp(true) + with(gradeSummaryAdapter) { + onCalculatedHelpClickListener = presenter::onCalculatedAverageHelpClick + onFinalHelpClickListener = presenter::onFinalAverageHelpClick + } - gradeSummaryRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.gradeSummaryRecycler) { + layoutManager = LinearLayoutManager(context) adapter = gradeSummaryAdapter } - gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() } - gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) + gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + gradeSummarySwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) + ) + gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() } + gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } - override fun updateData(data: List, header: GradeSummaryScrollableHeader) { - gradeSummaryAdapter.apply { - updateDataSet(data, true) - removeAllScrollableHeaders() - addScrollableHeader(header) + override fun updateData(data: List) { + with(gradeSummaryAdapter) { + items = data + notifyDataSetChanged() } } override fun clearView() { - gradeSummaryAdapter.clear() + with(gradeSummaryAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun resetView() { - gradeSummaryRecycler.scrollToPosition(0) + binding.gradeSummaryRecycler.scrollToPosition(0) } override fun showContent(show: Boolean) { - gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE + binding.gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE } override fun setErrorDetails(message: String) { - gradeSummaryErrorMessage.text = message + binding.gradeSummaryErrorMessage.text = message } override fun showProgress(show: Boolean) { - gradeSummaryProgress.visibility = if (show) VISIBLE else GONE + binding.gradeSummaryProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - gradeSummarySwipe.isEnabled = enable + binding.gradeSummarySwipe.isEnabled = enable } override fun showRefresh(show: Boolean) { - gradeSummarySwipe.isRefreshing = show + binding.gradeSummarySwipe.isRefreshing = show + } + + override fun showCalculatedAverageHelpDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.grade_summary_calculated_average_help_dialog_title) + .setMessage(R.string.grade_summary_calculated_average_help_dialog_message) + .setPositiveButton(R.string.all_close) { _, _ -> } + .show() + } + + override fun showFinalAverageHelpDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.grade_summary_final_average_help_dialog_title) + .setMessage(R.string.grade_summary_final_average_help_dialog_message) + .setPositiveButton(R.string.all_close) { _, _ -> } + .show() } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { @@ -125,7 +153,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt index 95c32d17..cf0f1d92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt @@ -1,64 +1,9 @@ package io.github.wulkanowy.ui.modules.grade.summary -import android.annotation.SuppressLint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_grade_summary.* -class GradeSummaryItem( - val summary: GradeSummary, - private val average: String -) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_grade_summary - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.run { - gradeSummaryItemTitle.text = summary.subject - gradeSummaryItemPoints.text = summary.pointsSum - gradeSummaryItemAverage.text = average - gradeSummaryItemPredicted.text = "${summary.predictedGrade} ${summary.proposedPoints}".trim() - gradeSummaryItemFinal.text = "${summary.finalGrade} ${summary.finalPoints}".trim() - - gradeSummaryItemPointsContainer.visibility = if (summary.pointsSum.isBlank()) GONE else VISIBLE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradeSummaryItem - - if (average != other.average) return false - if (summary != other.summary) return false - if (summary.id != other.summary.id) return false - - return true - } - - override fun hashCode(): Int { - var result = summary.hashCode() - result = 31 * result + summary.id.hashCode() - result = 31 * result + average.hashCode() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} +data class GradeSummaryItem( + val gradeSummary: GradeSummary, + val gradeDescriptive: GradeDescriptive? +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index c12f2a51..d762df02 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,29 +1,33 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC +import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE +import io.github.wulkanowy.data.enums.GradeSortingMode.DATE +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.calcAverage +import io.github.wulkanowy.ui.modules.grade.GradeSubject +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber -import java.lang.String.format -import java.util.Locale.FRANCE import javax.inject.Inject class GradeSummaryPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val gradeSummaryRepository: GradeSummaryRepository, - private val semesterRepository: SemesterRepository, + private val preferencesRepository: PreferencesRepository, private val averageProvider: GradeAverageProvider, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -35,38 +39,46 @@ class GradeSummaryPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { Timber.i("Loading grade summary data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it).map { semesters -> it to semesters } } - .flatMap { (student, semesters) -> - gradeSummaryRepository.getGradesSummary(student, semesters.first { it.semesterId == semesterId }, forceRefresh) - .map { it.sortedBy { subject -> subject.subject } } - .flatMap { gradesSummary -> - averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) - .map { averages -> createGradeSummaryItemsAndHeader(gradesSummary, averages) } - } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + + loadData(semesterId, forceRefresh) + if (!forceRefresh) view?.showErrorView(false) + } + + private fun loadData(semesterId: Int, forceRefresh: Boolean) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) + } + .logResourceStatus("load grade summary") + .mapResourceData { createGradeSummaryItems(it) } + .onResourceData { view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_summary", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) showRefresh(false) showProgress(false) - enableSwipe(true) notifyParentDataLoaded(semesterId) } - }.subscribe({ (gradeSummaryItems, gradeSummaryHeader) -> - Timber.i("Loading grade summary result: Success") - view?.run { - showEmpty(gradeSummaryItems.isEmpty()) - showContent(gradeSummaryItems.isNotEmpty()) - showErrorView(false) - updateData(gradeSummaryItems, gradeSummaryHeader) - } - analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading grade summary result: An exception occurred") - errorHandler.dispatch(it) - }) + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -112,34 +124,51 @@ class GradeSummaryPresenter @Inject constructor( showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } - private fun createGradeSummaryItemsAndHeader(gradesSummary: List, averages: List>): Pair, GradeSummaryScrollableHeader> { - return averages.filter { value -> value.second != 0.0 } - .let { filteredAverages -> - gradesSummary.filter { !checkEmpty(it, filteredAverages) } - .map { gradeSummary -> - GradeSummaryItem( - summary = gradeSummary, - average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "") - ) - }.let { - it to GradeSummaryScrollableHeader( - formatAverage(gradesSummary.calcAverage()), - formatAverage(filteredAverages.map { values -> values.second }.average())) + fun onCalculatedAverageHelpClick() { + view?.showCalculatedAverageHelpDialog() + } + + fun onFinalAverageHelpClick() { + view?.showFinalAverageHelpDialog() + } + + private fun createGradeSummaryItems(items: List): List { + return items + .filter { !checkEmpty(it) } + .let { gradeSubjects -> + when (preferencesRepository.gradeSortingMode) { + DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage -> + gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date } + + ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage -> + gradeDetailsWithAverage.subject.lowercase() + } + + AVERAGE -> gradeSubjects.sortedByDescending { it.average } + } } + .map { + val gradeSummary = it.summary.copy(average = it.average) + val descriptive = it.descriptive + GradeSummaryItem( + gradeSummary = gradeSummary, + gradeDescriptive = descriptive, + ) + } + } - private fun checkEmpty(gradeSummary: GradeSummary, averages: List>): Boolean { + private fun checkEmpty(gradeSummary: GradeSubject): Boolean { return gradeSummary.run { - finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null + summary.finalGrade.isBlank() + && summary.predictedGrade.isBlank() + && average == .0 + && points.isBlank() + && descriptive == null } } - - private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { - return if (average == 0.0) defaultValue - else format(FRANCE, "%.2f", average) - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt deleted file mode 100644 index f1c535c7..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryScrollableHeader.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.ui.modules.grade.summary - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.scrollable_header_grade_summary.* - -class GradeSummaryScrollableHeader(private val finalAverage: String, private val calculatedAverage: String) - : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.scrollable_header_grade_summary - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, - position: Int, payloads: MutableList?) { - holder?.apply { - gradeSummaryScrollableHeaderFinal.text = finalAverage - gradeSummaryScrollableHeaderCalculated.text = calculatedAverage - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GradeSummaryScrollableHeader - - if (calculatedAverage != other.calculatedAverage) return false - if (finalAverage != other.finalAverage) return false - - return true - } - - override fun hashCode(): Int { - var result = calculatedAverage.hashCode() - result = 31 * result + finalAverage.hashCode() - return result - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt index cf318487..36bd6142 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt @@ -12,7 +12,7 @@ interface GradeSummaryView : BaseView { fun initView() - fun updateData(data: List, header: GradeSummaryScrollableHeader) + fun updateData(data: List) fun resetView() @@ -32,6 +32,10 @@ interface GradeSummaryView : BaseView { fun showEmpty(show: Boolean) + fun showCalculatedAverageHelpDialog() + + fun showFinalAverageHelpDialog() + fun notifyParentDataLoaded(semesterId: Int) fun notifyParentRefresh() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt new file mode 100644 index 00000000..9dcd3d4a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkAdapter.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.ui.modules.homework + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.HeaderHomeworkBinding +import io.github.wulkanowy.databinding.ItemHomeworkBinding +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import java.time.LocalDate +import javax.inject.Inject + +class HomeworkAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList>() + + var onClickListener: (Homework) -> Unit = {} + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int) = items[position].viewType.id + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + HomeworkItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderHomeworkBinding.inflate(inflater, parent, false)) + HomeworkItem.ViewType.ITEM.id -> ItemViewHolder(ItemHomeworkBinding.inflate(inflater, parent, false)) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate) + is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Homework) + } + } + + @SuppressLint("DefaultLocale") + private fun bindHeaderViewHolder(binding: HeaderHomeworkBinding, date: LocalDate) { + with(binding) { + homeworkHeaderDay.text = date.weekDayName.capitalise() + homeworkHeaderDate.text = date.toFormattedString() + } + } + + private fun bindItemViewHolder(binding: ItemHomeworkBinding, homework: Homework) { + with(binding) { + homeworkItemSubject.text = homework.subject + homeworkItemTeacher.text = homework.teacher + homeworkItemContent.text = homework.content + homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE + homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(homework) } + } + } + + class HeaderViewHolder(val binding: HeaderHomeworkBinding) : + RecyclerView.ViewHolder(binding.root) + + class ItemViewHolder(val binding: ItemHomeworkBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index a011a015..0381acf3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -1,33 +1,33 @@ package io.github.wulkanowy.ui.modules.homework import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.FragmentHomeworkBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.homework.add.HomeworkAddDialog import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_homework.* +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { +@AndroidEntryPoint +class HomeworkFragment : BaseFragment(R.layout.fragment_homework), + HomeworkView, MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: HomeworkPresenter @Inject - lateinit var homeworkAdapter: FlexibleAdapter> + lateinit var homeworkAdapter: HomeworkAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -37,95 +37,110 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView { override val titleStringId get() = R.string.homework_title - override val isViewEmpty get() = homeworkAdapter.isEmpty + override val isViewEmpty get() = homeworkAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_homework, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = homeworkRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentHomeworkBinding.bind(view) + messageContainer = binding.homeworkRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { - homeworkAdapter.setOnItemClickListener(presenter::onHomeworkItemSelected) + homeworkAdapter.onClickListener = presenter::onHomeworkItemSelected - with(homeworkRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.homeworkRecycler) { + layoutManager = LinearLayoutManager(context) adapter = homeworkAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false)) + addItemDecoration(DividerItemDecoration(context)) } - homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - homeworkErrorRetry.setOnClickListener { presenter.onRetry() } - homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + homeworkSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + homeworkSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + homeworkErrorRetry.setOnClickListener { presenter.onRetry() } + homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() } - homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } - homeworkNextButton.setOnClickListener { presenter.onNextDay() } + homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } + homeworkNextButton.setOnClickListener { presenter.onNextDay() } - homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() } + + homeworkNavContainer.elevation = requireContext().dpToPx(3f) + } } - override fun updateData(data: List) { - homeworkAdapter.updateDataSet(data, true) - } - - fun onReloadList() { - presenter.reloadData() + override fun updateData(data: List>) { + with(homeworkAdapter) { + items = data + notifyDataSetChanged() + } } override fun clearData() { - homeworkAdapter.clear() + with(homeworkAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun updateNavigationWeek(date: String) { - homeworkNavDate.text = date + binding.homeworkNavDate.text = date } - override fun hideRefresh() { - homeworkSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.homeworkSwipe.isRefreshing = show } override fun showEmpty(show: Boolean) { - homeworkEmpty.visibility = if (show) VISIBLE else GONE + binding.homeworkEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - homeworkError.visibility = if (show) VISIBLE else GONE + binding.homeworkError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - homeworkErrorMessage.text = message + binding.homeworkErrorMessage.text = message } override fun showProgress(show: Boolean) { - homeworkProgress.visibility = if (show) VISIBLE else GONE + binding.homeworkProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - homeworkSwipe.isEnabled = enable + binding.homeworkSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - homeworkRecycler.visibility = if (show) VISIBLE else GONE + binding.homeworkRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - homeworkPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.homeworkPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showNextButton(show: Boolean) { - homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE } - override fun showTimetableDialog(homework: Homework) { + override fun showHomeworkDialog(homework: Homework) { (activity as? MainActivity)?.showDialogFragment(HomeworkDetailsDialog.newInstance(homework)) } + override fun showAddHomeworkDialog() { + (activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog()) + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun resetView() { + binding.homeworkRecycler.smoothScrollToPosition(0) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt deleted file mode 100644 index 49023788..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkHeader.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.wulkanowy.ui.modules.homework - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractHeaderItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.ExpandableViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.weekDayName -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_homework.* -import org.threeten.bp.LocalDate - -class HomeworkHeader(private val date: LocalDate) : AbstractHeaderItem() { - - override fun getLayoutRes() = R.layout.header_homework - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder( - adapter: FlexibleAdapter>?, holder: HomeworkHeader.ViewHolder, - position: Int, payloads: MutableList? - ) { - holder.run { - homeworkHeaderDay.text = date.weekDayName.capitalize() - homeworkHeaderDate.text = date.toFormattedString() - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HomeworkHeader - - if (date != other.date) return false - - return true - } - - override fun hashCode(): Int { - return date.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt index 3c2dd7ba..7e003958 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkItem.kt @@ -1,53 +1,9 @@ package io.github.wulkanowy.ui.modules.homework -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractSectionableItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Homework -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_homework.* +data class HomeworkItem(val value: T, val viewType: ViewType) { -class HomeworkItem(header: HomeworkHeader, val homework: Homework) : - AbstractSectionableItem(header) { - - override fun getLayoutRes() = R.layout.item_homework - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - homeworkItemSubject.text = homework.subject - homeworkItemTeacher.text = homework.teacher - homeworkItemContent.text = homework.content - homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE - homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HomeworkItem - - if (homework != other.homework) return false - return true - } - - override fun hashCode(): Int { - var result = homework.hashCode() - result = 31 * result + homework.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index aae18df3..6b263e26 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,34 +1,28 @@ package io.github.wulkanowy.ui.modules.homework -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.friday -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.toFormattedString -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.ofEpochDay +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.util.concurrent.TimeUnit +import java.time.LocalDate +import java.time.LocalDate.ofEpochDay import javax.inject.Inject class HomeworkPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay @@ -42,24 +36,24 @@ class HomeworkPresenter @Inject constructor( view.initView() Timber.i("Homework view was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(ofEpochDay(date ?: baseDate.toEpochDay())) + reloadView(ofEpochDay(date ?: baseDate.toEpochDay())) + loadData() if (currentDate.isHolidays) setBaseDateOnHolidays() - reloadView() } fun onPreviousDay() { - loadData(currentDate.minusDays(7)) - reloadView() + reloadView(currentDate.minusDays(7)) + loadData() } fun onNextDay() { - loadData(currentDate.plusDays(7)) - reloadView() + reloadView(currentDate.plusDays(7)) + loadData() } fun onSwipeRefresh() { Timber.i("Force refreshing the homework") - loadData(currentDate, true) + loadData(true) } fun onRetry() { @@ -67,76 +61,79 @@ class HomeworkPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(currentDate, true) + loadData(true) } fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } - fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is HomeworkItem) { - Timber.i("Select homework item ${item.homework.id}") - view?.showTimetableDialog(item.homework) - } + fun onHomeworkItemSelected(homework: Homework) { + Timber.i("Select homework item ${homework.id}") + view?.showHomeworkDialog(homework) + } + + fun onHomeworkAddButtonClicked() { + view?.showAddHomeworkDialog() } private fun setBaseDateOnHolidays() { - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) currentDate = baseDate reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + } + .launch("holidays") } - fun reloadData() { - loadData(currentDate, false) - } - - private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading homework data started") - currentDate = date - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) - } - } - .delay(200, TimeUnit.MILLISECONDS) - .map { it.groupBy { homework -> homework.date }.toSortedMap() } - .map { createHomeworkItem(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading homework result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - } - analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading homework result: An exception occurred") - errorHandler.dispatch(it) - }) + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + homeworkRepository.getHomework( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) } + .logResourceStatus("loading homework") + .mapResourceData { createHomeworkItem(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "homework", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -150,15 +147,18 @@ class HomeworkPresenter @Inject constructor( } } - private fun createHomeworkItem(items: Map>): List { - return items.flatMap { - HomeworkHeader(it.key).let { header -> - it.value.reversed().map { item -> HomeworkItem(header, item) } - } - } + private fun createHomeworkItem(items: List): List> { + return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> + listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed() + .map { exam -> + HomeworkItem(exam, HomeworkItem.ViewType.ITEM) + } + }.flatten() } - private fun reloadView() { + private fun reloadView(date: LocalDate) { + currentDate = date + Timber.i("Reload homework view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) @@ -175,8 +175,23 @@ class HomeworkPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.friday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) + } + } + + fun onViewReselected() { + Timber.i("Homework view is reselected") + + baseDate = LocalDate.now().nextOrSameSchoolDay + + if (currentDate != baseDate) { + reloadView(baseDate) + loadData() + } else if (view?.isViewEmpty == false) { + view?.resetView() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt index 1d241df4..56ba6c89 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt @@ -9,13 +9,13 @@ interface HomeworkView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List>) fun clearData() fun updateNavigationWeek(date: String) - fun hideRefresh() + fun showRefresh(show: Boolean) fun showEmpty(show: Boolean) @@ -33,5 +33,9 @@ interface HomeworkView : BaseView { fun showNextButton(show: Boolean) - fun showTimetableDialog(homework: Homework) + fun showHomeworkDialog(homework: Homework) + + fun showAddHomeworkDialog() + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt new file mode 100644 index 00000000..400d9f46 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -0,0 +1,112 @@ +package io.github.wulkanowy.ui.modules.homework.add + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogHomeworkAddBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class HomeworkAddDialog : BaseDialogFragment(), HomeworkAddView { + + @Inject + lateinit var presenter: HomeworkAddPresenter + + //todo: move it to presenter + private var date: LocalDate? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkAddBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun initView() { + with(binding) { + homeworkDialogSubjectEdit.doOnTextChanged { _, _, _, _ -> + homeworkDialogSubject.error = null + homeworkDialogSubject.isErrorEnabled = false + } + homeworkDialogDateEdit.doOnTextChanged { _, _, _, _ -> + homeworkDialogDate.error = null + homeworkDialogDate.isErrorEnabled = false + } + homeworkDialogContentEdit.doOnTextChanged { _, _, _, _ -> + homeworkDialogContent.error = null + homeworkDialogContent.isErrorEnabled = false + } + homeworkDialogClose.setOnClickListener { dismiss() } + homeworkDialogDateEdit.setOnClickListener { presenter.showDatePicker(date) } + homeworkDialogAdd.setOnClickListener { + presenter.onAddHomeworkClicked( + subject = homeworkDialogSubjectEdit.text?.toString(), + teacher = homeworkDialogTeacherEdit.text?.toString(), + date = homeworkDialogDateEdit.text?.toString(), + content = homeworkDialogContentEdit.text?.toString() + ) + } + } + } + + override fun showSuccessMessage() { + showMessage(getString(R.string.homework_add_success)) + } + + override fun setErrorSubjectRequired() { + with(binding.homeworkDialogSubject) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorDateRequired() { + with(binding.homeworkDialogDate) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorContentRequired() { + with(binding.homeworkDialogContent) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun closeDialog() { + dismiss() + } + + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = LocalDate.now(), + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + date = it + if (isAdded) { + binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString()) + } + } + ) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt new file mode 100644 index 00000000..a21f6aef --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt @@ -0,0 +1,87 @@ +package io.github.wulkanowy.ui.modules.homework.add + +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.toLocalDate +import timber.log.Timber +import java.time.LocalDate +import javax.inject.Inject + +class HomeworkAddPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val homeworkRepository: HomeworkRepository, + private val semesterRepository: SemesterRepository +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: HomeworkAddView) { + super.onAttachView(view) + view.initView() + Timber.i("Homework details view was initialized") + } + + fun showDatePicker(date: LocalDate?) { + view?.showDatePickerDialog(date ?: LocalDate.now()) + } + + fun onAddHomeworkClicked(subject: String?, teacher: String?, date: String?, content: String?) { + var isError = false + + if (subject.isNullOrBlank()) { + view?.setErrorSubjectRequired() + isError = true + } + + if (date.isNullOrBlank()) { + view?.setErrorDateRequired() + isError = true + } + + if (content.isNullOrBlank()) { + view?.setErrorContentRequired() + isError = true + } + + if (!isError) { + saveHomework(subject!!, teacher.orEmpty(), date!!.toLocalDate(), content!!) + } + } + + private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) { + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + val entryDate = LocalDate.now() + homeworkRepository.saveHomework( + Homework( + semesterId = semester.semesterId, + studentId = student.studentId, + date = date, + entryDate = entryDate, + subject = subject, + content = content, + teacher = teacher, + teacherSymbol = "", + attachments = emptyList(), + ).apply { isAddedByUser = true } + ) + } + .logResourceStatus("homework insert") + .onResourceSuccess { + view?.run { + showSuccessMessage() + closeDialog() + } + } + .onResourceError(errorHandler::dispatch) + .launch("add_homework") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt new file mode 100644 index 00000000..91414ae2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.modules.homework.add + +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate + +interface HomeworkAddView : BaseView { + + fun initView() + + fun showSuccessMessage() + + fun setErrorSubjectRequired() + + fun setErrorDateRequired() + + fun setErrorContentRequired() + + fun closeDialog() + + fun showDatePickerDialog(selectedDate: LocalDate) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt index 3706b56e..1ad2a0e3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt @@ -1,14 +1,17 @@ package io.github.wulkanowy.ui.modules.homework.details import android.view.LayoutInflater -import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentBinding +import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentsHeaderBinding +import io.github.wulkanowy.databinding.ItemHomeworkDialogDetailsBinding +import io.github.wulkanowy.utils.ifNullOrBlank import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.item_homework_dialog_attachment.view.* -import kotlinx.android.synthetic.main.item_homework_dialog_details.view.* import javax.inject.Inject class HomeworkDetailsAdapter @Inject constructor() : @@ -30,6 +33,8 @@ class HomeworkDetailsAdapter @Inject constructor() : var onAttachmentClickListener: (url: String) -> Unit = {} + var onDeleteClickListener: (homework: Homework) -> Unit = {} + override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0 override fun getItemViewType(position: Int) = when (position) { @@ -42,9 +47,15 @@ class HomeworkDetailsAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder(inflater.inflate(R.layout.item_homework_dialog_attachments_header, parent, false)) - ViewType.ATTACHMENT.id -> AttachmentViewHolder(inflater.inflate(R.layout.item_homework_dialog_attachment, parent, false)) - else -> DetailsViewHolder(inflater.inflate(R.layout.item_homework_dialog_details, parent, false)) + ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder( + ItemHomeworkDialogAttachmentsHeaderBinding.inflate(inflater, parent, false) + ) + ViewType.ATTACHMENT.id -> AttachmentViewHolder( + ItemHomeworkDialogAttachmentBinding.inflate(inflater, parent, false) + ) + else -> DetailsViewHolder( + ItemHomeworkDialogDetailsBinding.inflate(inflater, parent, false) + ) } } @@ -56,29 +67,38 @@ class HomeworkDetailsAdapter @Inject constructor() : } private fun bindDetailsViewHolder(holder: DetailsViewHolder) { - with(holder.view) { + val noDataString = holder.binding.root.context.getString(R.string.all_no_data) + + with(holder.binding) { homeworkDialogDate.text = homework?.date?.toFormattedString() homeworkDialogEntryDate.text = homework?.entryDate?.toFormattedString() - homeworkDialogSubject.text = homework?.subject - homeworkDialogTeacher.text = homework?.teacher - homeworkDialogContent.text = homework?.content + homeworkDialogSubject.text = homework?.subject.ifNullOrBlank { noDataString } + homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString } + homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString } + homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE + homeworkDialogDelete.setOnClickListener { + onDeleteClickListener(homework!!) + } } } private fun bindAttachmentViewHolder(holder: AttachmentViewHolder, position: Int) { val item = attachments[position] - with(holder.view) { + with(holder.binding) { homeworkDialogAttachment.text = item.second - setOnClickListener { + root.setOnClickListener { onAttachmentClickListener(item.first) } } } - class DetailsViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class DetailsViewHolder(val binding: ItemHomeworkDialogDetailsBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentsHeaderViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentsHeaderViewHolder(val binding: ItemHomeworkDialogAttachmentsHeaderBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentViewHolder(val binding: ItemHomeworkDialogAttachmentBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 54cb5c68..1f9bc881 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -1,20 +1,23 @@ package io.github.wulkanowy.ui.modules.homework.details import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.databinding.DialogHomeworkBinding import io.github.wulkanowy.ui.base.BaseDialogFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.utils.openInternetBrowser -import kotlinx.android.synthetic.main.dialog_homework.* +import io.github.wulkanowy.utils.serializable import javax.inject.Inject -class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { +@AndroidEntryPoint +class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { @Inject lateinit var presenter: HomeworkDetailsPresenter @@ -22,53 +25,62 @@ class HomeworkDetailsDialog : BaseDialogFragment(), HomeworkDetailsView { @Inject lateinit var detailsAdapter: HomeworkDetailsAdapter + override val homeworkDeleteSuccess: String + get() = getString(R.string.homework_delete_success) + private lateinit var homework: Homework companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(homework: Homework): HomeworkDetailsDialog { - return HomeworkDetailsDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) } - } + fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to homework) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - homework = getSerializable(ARGUMENT_KEY) as Homework - } + homework = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_homework, container, false) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) } @SuppressLint("SetTextI18n") override fun initView() { - homeworkDialogRead.text = view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) - homeworkDialogRead.setOnClickListener { presenter.toggleDone(homework) } - homeworkDialogClose.setOnClickListener { dismiss() } + with(binding) { + homeworkDialogRead.text = + view?.context?.getString(if (homework.isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) + homeworkDialogRead.setOnClickListener { presenter.toggleDone(homework) } + homeworkDialogClose.setOnClickListener { dismiss() } + } - with(homeworkDialogRecycler) { + with(binding.homeworkDialogRecycler) { layoutManager = LinearLayoutManager(context) adapter = detailsAdapter.apply { onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } + onDeleteClickListener = { homework -> presenter.deleteHomework(homework) } homework = this@HomeworkDetailsDialog.homework } } } + override fun closeDialog() { + dismiss() + } + override fun updateMarkAsDoneLabel(isDone: Boolean) { - (parentFragment as? HomeworkFragment)?.onReloadList() - homeworkDialogRead.text = view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) + binding.homeworkDialogRead.text = + view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index 04a97b8c..84933f06 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -1,22 +1,24 @@ package io.github.wulkanowy.ui.modules.homework.details import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject class HomeworkDetailsPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper, +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: HomeworkDetailsView) { super.onAttachView(view) @@ -24,21 +26,27 @@ class HomeworkDetailsPresenter @Inject constructor( Timber.i("Homework details view was initialized") } - fun toggleDone(homework: Homework) { - Timber.i("Homework details update start") - disposable.add(homeworkRepository.toggleDone(homework) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Homework details update: Success") + fun deleteHomework(homework: Homework) { + resourceFlow { homeworkRepository.deleteHomework(homework) } + .logResourceStatus("homework delete") + .onResourceSuccess { view?.run { - updateMarkAsDoneLabel(homework.isDone) + showMessage(homeworkDeleteSuccess) + closeDialog() } - analytics.logEvent("homework_mark_as_done") - }) { - Timber.i("Homework details update result: An exception occurred") - errorHandler.dispatch(it) } - ) + .onResourceError(errorHandler::dispatch) + .launch("delete") + } + + fun toggleDone(homework: Homework) { + resourceFlow { homeworkRepository.toggleDone(homework) } + .logResourceStatus("homework details update") + .onResourceSuccess { + view?.updateMarkAsDoneLabel(homework.isDone) + analytics.logEvent("homework_mark_as_done") + } + .onResourceError(errorHandler::dispatch) + .launch("toggle") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt index 697f2233..4a47de43 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt @@ -4,7 +4,11 @@ import io.github.wulkanowy.ui.base.BaseView interface HomeworkDetailsView : BaseView { + val homeworkDeleteSuccess: String + fun initView() + fun closeDialog() + fun updateMarkAsDoneLabel(isDone: Boolean) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index 19f23593..e528c514 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -2,43 +2,59 @@ package io.github.wulkanowy.ui.modules.login import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build.VERSION_CODES.TIRAMISU import android.os.Bundle import android.view.MenuItem +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE +import androidx.fragment.app.commit +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.databinding.ActivityLoginBinding import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment -import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.activity_login.* +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.InAppUpdateHelper import javax.inject.Inject -class LoginActivity : BaseActivity(), LoginView { +@AndroidEntryPoint +class LoginActivity : BaseActivity(), LoginView { @Inject override lateinit var presenter: LoginPresenter @Inject - lateinit var loginAdapter: BaseFragmentPagerAdapter + lateinit var inAppUpdateHelper: InAppUpdateHelper + + @Inject + lateinit var appInfo: AppInfo companion object { - fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) } - override val currentViewIndex get() = loginViewpager.currentItem - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_login) - setSupportActionBar(loginToolbar) - messageContainer = loginContainer + setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root) + setSupportActionBar(binding.loginToolbar) + messageContainer = binding.loginContainer + inAppUpdateHelper.messageContainer = binding.loginContainer presenter.onAttachView(this) + inAppUpdateHelper.checkAndInstallUpdates() + + if (savedInstanceState == null) { + openFragment(LoginFormFragment.newInstance(), clearBackStack = true) + } } override fun initView() { @@ -46,63 +62,62 @@ class LoginActivity : BaseActivity(), LoginView { setDisplayHomeAsUpEnabled(true) setDisplayShowTitleEnabled(false) } - - with(loginAdapter) { - containerId = loginViewpager.id - addFragments(listOf( - LoginFormFragment.newInstance(), - LoginSymbolFragment.newInstance(), - LoginStudentSelectFragment.newInstance(), - LoginAdvancedFragment.newInstance(), - LoginRecoverFragment.newInstance() - )) - } - - with(loginViewpager) { - offscreenPageLimit = 2 - adapter = loginAdapter - setOnSelectPageListener(presenter::onViewSelected) - } } override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) onBackPressed() + if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed() return true } - override fun switchView(index: Int) { - loginViewpager.setCurrentItem(index, false) - } - - override fun showActionBar(show: Boolean) { + fun showActionBar(show: Boolean) { supportActionBar?.run { if (show) show() else hide() } } - override fun onBackPressed() { - presenter.onBackPressed { super.onBackPressed() } + fun navigateToSymbolFragment(loginData: LoginData) { + openFragment(LoginSymbolFragment.newInstance(loginData)) } - override fun notifyInitSymbolFragment(loginData: Triple) { - (loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(loginData) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser)) } - override fun notifyInitStudentSelectFragment(students: List) { - (loginAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)?.onParentInitStudentSelectFragment(students) + fun navigateToNotifications() { + val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU + val isPermissionGranted = ContextCompat.checkSelfPermission( + this, "android.permission.POST_NOTIFICATIONS" + ) == PackageManager.PERMISSION_GRANTED + + if (isNotificationsPermissionRequired && !isPermissionGranted) { + openFragment(NotificationsFragment.newInstance(), clearBackStack = true) + } else navigateToFinish() } - fun onFormFragmentAccountLogged(students: List, loginData: Triple) { - presenter.onFormViewAccountLogged(students, loginData) - } - - fun onSymbolFragmentAccountLogged(students: List) { - presenter.onSymbolViewAccountLogged(students) + fun navigateToFinish() { + startActivity(MainActivity.getStartIntent(this)) + finish() } fun onAdvancedLoginClick() { - presenter.onAdvancedLoginClick() + openFragment(LoginAdvancedFragment.newInstance()) } fun onRecoverClick() { - presenter.onRecoverClick() + openFragment(LoginRecoverFragment.newInstance()) + } + + private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) { + supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE) + + supportFragmentManager.commit { + replace(R.id.loginContainer, fragment) + setReorderingAllowed(true) + if (!clearBackStack) addToBackStack(fragment::class.java.name) + } + } + + override fun onResume() { + super.onResume() + inAppUpdateHelper.onResume() + presenter.updateSdkMappings() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt new file mode 100644 index 00000000..b066cceb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.login + +import java.io.Serializable + +data class LoginData( + val login: String, + val password: String, + val baseUrl: String, + val domainSuffix: String, + val defaultSymbol: String, + val userEnteredSymbol: String? = null, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt index 5ed181bf..4f709438 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt @@ -1,23 +1,24 @@ package io.github.wulkanowy.ui.modules.login -import android.content.res.Resources +import android.content.Context import android.database.sqlite.SQLiteConstraintException -import com.chuckerteam.chucker.api.ChuckerCollector +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R -import io.github.wulkanowy.sdk.exception.BadCredentialsException -import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException -import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException -import io.github.wulkanowy.sdk.mobile.exception.InvalidTokenException -import io.github.wulkanowy.sdk.mobile.exception.TokenDeadException +import io.github.wulkanowy.sdk.hebe.exception.InvalidPinException +import io.github.wulkanowy.sdk.hebe.exception.InvalidTokenException +import io.github.wulkanowy.sdk.hebe.exception.TokenDeadException +import io.github.wulkanowy.sdk.hebe.exception.UnknownTokenException +import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject +import io.github.wulkanowy.sdk.hebe.exception.InvalidSymbolException as InvalidHebeSymbolException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException as InvalidScrapperSymbolException class LoginErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { + @ApplicationContext context: Context, +) : ErrorHandler(context) { - var onBadCredentials: () -> Unit = {} + var onBadCredentials: (String?) -> Unit = {} var onInvalidToken: (String) -> Unit = {} @@ -28,13 +29,16 @@ class LoginErrorHandler @Inject constructor( var onStudentDuplicate: (String) -> Unit = {} override fun proceed(error: Throwable) { + val resources = context.resources when (error) { - is BadCredentialsException -> onBadCredentials() + is BadCredentialsException -> onBadCredentials(error.message) is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student)) is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token)) + is UnknownTokenException, is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token)) is InvalidPinException -> onInvalidPin(resources.getString(R.string.login_invalid_pin)) - is InvalidSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol)) + is InvalidScrapperSymbolException, + is InvalidHebeSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol)) else -> super.proceed(error) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt deleted file mode 100644 index 26a243dc..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.wulkanowy.ui.modules.login - -import dagger.Module -import dagger.Provides -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.di.scopes.PerActivity -import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter -import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment -import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment -import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment -import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment -import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment - -@Suppress("unused") -@Module -internal abstract class LoginModule { - - companion object { - - @PerActivity - @Provides - fun provideLoginAdapter(activity: LoginActivity) = BaseFragmentPagerAdapter(activity.supportFragmentManager) - } - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLoginFormFragment(): LoginFormFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLoginAdvancedFragment(): LoginAdvancedFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLoginSymbolFragment(): LoginSymbolFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLoginSelectStudentFragment(): LoginStudentSelectFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLoginRecoverFragment(): LoginRecoverFragment -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt index 16342de4..aff0515f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt @@ -1,74 +1,29 @@ package io.github.wulkanowy.ui.modules.login -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.WulkanowyRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject class LoginPresenter @Inject constructor( - schedulers: SchedulersProvider, + private val wulkanowyRepository: WulkanowyRepository, errorHandler: ErrorHandler, studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: LoginView) { super.onAttachView(view) - with(view) { - initView() - showActionBar(false) - } + view.initView() Timber.i("Login view was initialized") } - fun onFormViewAccountLogged(students: List, loginData: Triple) { - view?.apply { - if (students.isEmpty()) { - Timber.i("Switch to symbol form") - notifyInitSymbolFragment(loginData) - switchView(1) - } else { - Timber.i("Switch to student select") - notifyInitStudentSelectFragment(students) - switchView(2) - } - } - } - - fun onSymbolViewAccountLogged(students: List) { - view?.apply { - Timber.i("Switch to student select") - notifyInitStudentSelectFragment(students) - switchView(2) - } - } - - fun onAdvancedLoginClick() { - view?.switchView(3) - } - - fun onRecoverClick() { - view?.switchView(4) - } - - fun onViewSelected(index: Int) { - view?.apply { - when (index) { - 0 -> showActionBar(false) - 1, 2, 3, 4 -> showActionBar(true) - } - } - } - - fun onBackPressed(default: () -> Unit) { - Timber.i("Back pressed in login view") - view?.apply { - when (currentViewIndex) { - 1, 2, 3, 4 -> switchView(0) - else -> default() - } + fun updateSdkMappings() { + presenterScope.launch { + runCatching { wulkanowyRepository.fetchMapping() } + .onFailure { Timber.e(it) } } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt index 9c98a1d8..a0949e6d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt @@ -1,19 +1,8 @@ package io.github.wulkanowy.ui.modules.login -import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.ui.base.BaseView interface LoginView : BaseView { - val currentViewIndex: Int - fun initView() - - fun switchView(index: Int) - - fun showActionBar(show: Boolean) - - fun notifyInitSymbolFragment(loginData: Triple) - - fun notifyInitStudentSelectFragment(students: List) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt index aaea31ec..13d2c14a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt @@ -1,26 +1,30 @@ package io.github.wulkanowy.ui.modules.login.advanced import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.ArrayAdapter +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.form.LoginSymbolAdapter import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.setOnEditorDoneSignIn import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_advanced.* import javax.inject.Inject -class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { +@AndroidEntryPoint +class LoginAdvancedFragment : + BaseFragment(R.layout.fragment_login_advanced), + LoginAdvancedView { @Inject lateinit var presenter: LoginAdvancedPresenter @@ -30,17 +34,17 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override val formLoginType: String - get() = when (loginTypeSwitch.checkedRadioButtonId) { - R.id.loginTypeApi -> "API" - R.id.loginTypeScrapper -> "SCRAPPER" - else -> "HYBRID" + get() = when (binding.loginTypeSwitch.checkedRadioButtonId) { + R.id.loginTypeApi -> Sdk.Mode.HEBE.name + R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER.name + else -> Sdk.Mode.HYBRID.name } override val formUsernameValue: String - get() = loginFormUsername.text.toString().trim() + get() = binding.loginFormUsername.text.toString().trim() override val formPassValue: String - get() = loginFormPass.text.toString().trim() + get() = binding.loginFormPass.text.toString().trim() private lateinit var hostKeys: Array @@ -49,19 +53,24 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { private lateinit var hostSymbols: Array override val formHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) + .orEmpty() + + override val formDomainSuffix: String + get() = binding.loginFormDomainSuffix.text.toString().trim() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) + .orEmpty() override val formPinValue: String - get() = loginFormPin.text.toString().trim() + get() = binding.loginFormPin.text.toString().trim() override val formSymbolValue: String - get() = loginFormSymbol.text.toString().trim() + get() = binding.loginFormSymbol.text.toString().trim() override val formTokenValue: String - get() = loginFormToken.text.toString().trim() + get() = binding.loginFormToken.text.toString().trim() override val nicknameLabel: String get() = getString(R.string.login_nickname_hint) @@ -69,205 +78,240 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { override val emailLabel: String get() = getString(R.string.login_email_hint) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_advanced, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginAdvancedBinding.bind(view) presenter.onAttachView(this) } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } - loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } - loginFormPin.doOnTextChanged { _, _, _, _ -> presenter.onPinTextChanged() } - loginFormSymbol.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } - loginFormToken.doOnTextChanged { _, _, _, _ -> presenter.onTokenTextChanged() } - loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginFormSignIn.setOnClickListener { presenter.onSignInClick() } + with(binding) { + loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } + loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } + loginFormPin.doOnTextChanged { _, _, _, _ -> presenter.onPinTextChanged() } + loginFormSymbol.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } + loginFormToken.doOnTextChanged { _, _, _, _ -> presenter.onTokenTextChanged() } + loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginFormSignIn.setOnClickListener { presenter.onSignInClick() } - loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> - presenter.onLoginModeSelected(when (checkedId) { - R.id.loginTypeApi -> Sdk.Mode.API - R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER - else -> Sdk.Mode.HYBRID - }) + loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> + presenter.onLoginModeSelected( + when (checkedId) { + R.id.loginTypeApi -> Sdk.Mode.HEBE + R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER + else -> Sdk.Mode.HYBRID + } + ) + } + + loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + + loginFormSymbol.setAdapter( + ArrayAdapter( + requireContext(), + android.R.layout.simple_list_item_1, + resources.getStringArray(R.array.symbols_values) + ) + ) } - loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } - loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } - - loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) - - with(loginFormHost) { + with(binding.loginFormHost) { setText(hostKeys.getOrNull(0).orEmpty()) - setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginFormContainer.visibility == GONE) dismissDropDown() } + setAdapter( + LoginSymbolAdapter( + context, + R.layout.support_simple_spinner_dropdown_item, + hostKeys + ) + ) + setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() } } } override fun showMobileApiWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api) + binding.loginFormAdvancedWarningInfo.text = + getString(R.string.login_advanced_warning_mobile_api) } override fun showScraperWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper) + binding.loginFormAdvancedWarningInfo.text = + getString(R.string.login_advanced_warning_scraper) } override fun showHybridWarningMessage() { - loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid) + binding.loginFormAdvancedWarningInfo.text = + getString(R.string.login_advanced_warning_hybrid) } - override fun setDefaultCredentials(username: String, pass: String, symbol: String, token: String, pin: String) { - loginFormUsername.setText(username) - loginFormPass.setText(pass) - loginFormToken.setText(token) - loginFormSymbol.setText(symbol) - loginFormPin.setText(pin) + override fun setDefaultCredentials( + username: String, + pass: String, + symbol: String, + token: String, + pin: String + ) { + with(binding) { + loginFormUsername.setText(username) + loginFormPass.setText(pass) + loginFormToken.setText(token) + loginFormSymbol.setText(symbol) + loginFormPin.setText(pin) + } } override fun setUsernameLabel(label: String) { - loginFormUsernameLayout.hint = label + binding.loginFormUsernameLayout.hint = label } override fun setSymbol(symbol: String) { - loginFormSymbol.setText(symbol) + binding.loginFormSymbol.setText(symbol) } override fun setErrorUsernameRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setErrorLoginRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_login) } } override fun setErrorEmailRequired() { - with(loginFormUsernameLayout) { + with(binding.loginFormUsernameLayout) { requestFocus() error = getString(R.string.login_invalid_email) } } override fun setErrorPassRequired(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setErrorPassInvalid(focus: Boolean) { - with(loginFormPassLayout) { + with(binding.loginFormPassLayout) { if (focus) requestFocus() error = getString(R.string.login_invalid_password) } } - override fun setErrorPassIncorrect() { - with(loginFormPassLayout) { + override fun setErrorPassIncorrect(message: String?) { + with(binding.loginFormPassLayout) { requestFocus() - error = getString(R.string.login_incorrect_password) + error = message ?: getString(R.string.login_incorrect_password_default) } } override fun setErrorPinRequired() { - with(loginFormPinLayout) { + with(binding.loginFormPinLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setErrorPinInvalid(message: String) { - with(loginFormPinLayout) { + with(binding.loginFormPinLayout) { requestFocus() error = message } } override fun setErrorSymbolRequired() { - with(loginFormSymbolLayout) { + with(binding.loginFormSymbolLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setErrorSymbolInvalid(message: String) { - with(loginFormSymbolLayout) { + with(binding.loginFormSymbolLayout) { requestFocus() error = message } } override fun setErrorTokenRequired() { - with(loginFormTokenLayout) { + with(binding.loginFormTokenLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setErrorTokenInvalid(message: String) { - with(loginFormTokenLayout) { + with(binding.loginFormTokenLayout) { requestFocus() error = message } } override fun clearUsernameError() { - loginFormUsernameLayout.error = null + binding.loginFormUsernameLayout.error = null } override fun clearPassError() { - loginFormPassLayout.error = null + binding.loginFormPassLayout.error = null } override fun clearPinKeyError() { - loginFormPinLayout.error = null + binding.loginFormPinLayout.error = null } override fun clearSymbolError() { - loginFormSymbolLayout.error = null + binding.loginFormSymbolLayout.error = null } override fun clearTokenError() { - loginFormTokenLayout.error = null + binding.loginFormTokenLayout.error = null } override fun showOnlyHybridModeInputs() { - loginFormUsernameLayout.visibility = VISIBLE - loginFormPassLayout.visibility = VISIBLE - loginFormHostLayout.visibility = VISIBLE - loginFormPinLayout.visibility = GONE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = GONE + with(binding) { + loginFormUsernameLayout.visibility = VISIBLE + loginFormPassLayout.visibility = VISIBLE + loginFormHostLayout.visibility = VISIBLE + loginFormDomainSuffixLayout.isVisible = true + loginFormPinLayout.visibility = GONE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = GONE + } } override fun showOnlyScrapperModeInputs() { - loginFormUsernameLayout.visibility = VISIBLE - loginFormPassLayout.visibility = VISIBLE - loginFormHostLayout.visibility = VISIBLE - loginFormPinLayout.visibility = GONE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = GONE + with(binding) { + loginFormUsernameLayout.visibility = VISIBLE + loginFormPassLayout.visibility = VISIBLE + loginFormHostLayout.visibility = VISIBLE + loginFormDomainSuffixLayout.isVisible = true + loginFormPinLayout.visibility = GONE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = GONE + } } override fun showOnlyMobileApiModeInputs() { - loginFormUsernameLayout.visibility = GONE - loginFormPassLayout.visibility = GONE - loginFormHostLayout.visibility = GONE - loginFormPinLayout.visibility = VISIBLE - loginFormSymbolLayout.visibility = VISIBLE - loginFormTokenLayout.visibility = VISIBLE + with(binding) { + loginFormUsernameLayout.visibility = GONE + loginFormPassLayout.visibility = GONE + loginFormHostLayout.visibility = GONE + loginFormDomainSuffixLayout.isVisible = false + loginFormPinLayout.visibility = VISIBLE + loginFormSymbolLayout.visibility = VISIBLE + loginFormTokenLayout.visibility = VISIBLE + } } override fun showSoftKeyboard() { @@ -279,19 +323,19 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override fun showProgress(show: Boolean) { - loginFormProgress.visibility = if (show) VISIBLE else GONE + binding.loginFormProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginFormContainer.visibility = if (show) VISIBLE else GONE + binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } - override fun notifyParentAccountLogged(students: List) { - (activity as? LoginActivity)?.onFormFragmentAccountLogged(students, Triple( - loginFormUsername.text.toString(), - loginFormPass.text.toString(), - resources.getStringArray(R.array.hosts_values)[1] - )) + override fun navigateToSymbol(loginData: LoginData) { + (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) + } + + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun onResume() { @@ -300,7 +344,7 @@ class LoginAdvancedFragment : BaseFragment(), LoginAdvancedView { } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index 3a0d4a0d..009f26e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -1,23 +1,27 @@ package io.github.wulkanowy.ui.modules.login.advanced -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.ifNullOrBlank -import io.reactivex.Single +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject class LoginAdvancedPresenter @Inject constructor( - schedulers: SchedulersProvider, studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(loginErrorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(loginErrorHandler, studentRepository) { override fun onAttachView(view: LoginAdvancedView) { super.onAttachView(view) @@ -33,9 +37,9 @@ class LoginAdvancedPresenter @Inject constructor( } } - private fun onBadCredentials() { + private fun onBadCredentials(message: String?) { view?.run { - setErrorPassIncorrect() + setErrorPassIncorrect(message) showSoftKeyboard() Timber.i("Entered wrong username or password") } @@ -67,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor( fun updateUsernameLabel() { view?.apply { - setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel) + setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel) } } @@ -75,8 +79,10 @@ class LoginAdvancedPresenter @Inject constructor( view?.apply { clearPassError() clearUsernameError() - if (formHostValue.contains("fakelog")) { - setDefaultCredentials("jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999") + if (formHostValue.contains("wulkanowy")) { + setDefaultCredentials( + "jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999" + ) } setSymbol(formHostSymbol) updateUsernameLabel() @@ -86,14 +92,16 @@ class LoginAdvancedPresenter @Inject constructor( fun onLoginModeSelected(type: Sdk.Mode) { view?.run { when (type) { - Sdk.Mode.API -> { + Sdk.Mode.HEBE -> { showOnlyMobileApiModeInputs() showMobileApiWarningMessage() } + Sdk.Mode.SCRAPPER -> { showOnlyScrapperModeInputs() showScraperWarningMessage() } + Sdk.Mode.HYBRID -> { showOnlyHybridModeInputs() showHybridWarningMessage() @@ -125,47 +133,75 @@ class LoginAdvancedPresenter @Inject constructor( fun onSignInClick() { if (!validateCredentials()) return - disposable.add(getStudentsAppropriatesToLoginType() - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - hideSoftKeyboard() - showProgress(true) - showContent(false) + resourceFlow { getStudentsAppropriatesToLoginType() } + .logResourceStatus("login") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + hideSoftKeyboard() + showProgress(true) + showContent(false) + } + + is Resource.Success -> { + analytics.logEvent( + "registration_form", + "success" to true, + "scrapperBaseUrl" to view?.formHostValue.orEmpty(), + "error" to "No error" + ) + val loginData = LoginData( + login = view?.formUsernameValue.orEmpty().trim(), + password = view?.formPassValue.orEmpty().trim(), + baseUrl = view?.formHostValue.orEmpty().trim(), + domainSuffix = view?.formDomainSuffix.orEmpty().trim(), + defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), + ) + when (it.data.symbols.size) { + 0 -> view?.navigateToSymbol(loginData) + else -> view?.navigateToStudentSelect( + loginData = loginData, + registerUser = it.data, + ) + } + } + + is Resource.Error -> { + analytics.logEvent( + "registration_form", + "success" to false, "students" to -1, + "error" to it.error.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(it.error) + } } - Timber.i("Login started") - } - .doFinally { + }.onResourceNotLoading { view?.apply { showProgress(false) showContent(true) } - } - .subscribe({ - Timber.i("Login result: Success") - analytics.logEvent("registration_form", "success" to true, "students" to it.size, "error" to "No error") - view?.notifyParentAccountLogged(it) - }, { - Timber.i("Login result: An exception occurred") - analytics.logEvent("registration_form", "success" to false, "students" to -1, "error" to it.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it) - })) + }.launch("login") } - private fun getStudentsAppropriatesToLoginType(): Single> { + private suspend fun getStudentsAppropriatesToLoginType(): RegisterUser { val email = view?.formUsernameValue.orEmpty() val password = view?.formPassValue.orEmpty() val endpoint = view?.formHostValue.orEmpty() + val domainSuffix = view?.formDomainSuffix.orEmpty() val pin = view?.formPinValue.orEmpty() val symbol = view?.formSymbolValue.orEmpty() val token = view?.formTokenValue.orEmpty() - return when (Sdk.Mode.valueOf(view?.formLoginType ?: "")) { - Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token) - Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(email, password, endpoint, symbol) - Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(email, password, endpoint, symbol) + return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { + Sdk.Mode.HEBE -> studentRepository.getStudentsApi(pin, symbol, token) + Sdk.Mode.SCRAPPER -> studentRepository.getUserSubjectsFromScrapper( + email, password, endpoint, domainSuffix, symbol + ) + + Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid( + email, password, endpoint, symbol + ) } } @@ -181,8 +217,8 @@ class LoginAdvancedPresenter @Inject constructor( var isCorrect = true - when (Sdk.Mode.valueOf(view?.formLoginType ?: "")) { - Sdk.Mode.API -> { + when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { + Sdk.Mode.HEBE -> { if (pin.isEmpty()) { view?.setErrorPinRequired() isCorrect = false @@ -198,17 +234,17 @@ class LoginAdvancedPresenter @Inject constructor( isCorrect = false } } + Sdk.Mode.HYBRID, Sdk.Mode.SCRAPPER -> { if (login.isEmpty()) { view?.setErrorUsernameRequired() isCorrect = false } else { - if ("@" in login && "standard" !in host) { + if ("@" in login && "login" in host) { view?.setErrorLoginRequired() isCorrect = false } - - if ("@" !in login && "standard" in host) { + if ("@" !in login && "email" in host) { view?.setErrorEmailRequired() isCorrect = false } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt index 36c9a93f..afd33e3b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt @@ -1,7 +1,8 @@ package io.github.wulkanowy.ui.modules.login.advanced -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData interface LoginAdvancedView : BaseView { @@ -11,6 +12,8 @@ interface LoginAdvancedView : BaseView { val formHostValue: String + val formDomainSuffix: String + val formHostSymbol: String val formLoginType: String @@ -49,7 +52,7 @@ interface LoginAdvancedView : BaseView { fun setErrorPassInvalid(focus: Boolean) - fun setErrorPassIncorrect() + fun setErrorPassIncorrect(message: String?) fun clearUsernameError() @@ -69,7 +72,9 @@ interface LoginAdvancedView : BaseView { fun showContent(show: Boolean) - fun notifyParentAccountLogged(students: List) + fun navigateToSymbol(loginData: LoginData) + + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun setErrorPinRequired() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt index 058e702e..1c492069 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt @@ -2,26 +2,31 @@ package io.github.wulkanowy.ui.modules.login.form import android.annotation.SuppressLint import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.setFragmentResultListener +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.hideSoftInput -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnEditorDoneSignIn -import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_form.* +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo +import io.github.wulkanowy.utils.* import javax.inject.Inject -class LoginFormFragment : BaseFragment(), LoginFormView { +@AndroidEntryPoint +class LoginFormFragment : BaseFragment(R.layout.fragment_login_form), + LoginFormView { @Inject lateinit var presenter: LoginFormPresenter @@ -29,21 +34,29 @@ class LoginFormFragment : BaseFragment(), LoginFormView { @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { fun newInstance() = LoginFormFragment() } override val formUsernameValue: String - get() = loginFormUsername.text.toString() + get() = binding.loginFormUsername.text.toString() override val formPassValue: String - get() = loginFormPass.text.toString() + get() = binding.loginFormPass.text.toString() override val formHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) + .orEmpty() + + override val formDomainSuffix: String + get() = binding.loginFormDomainSuffix.text.toString() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginFormHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) + .orEmpty() override val nicknameLabel: String get() = getString(R.string.login_nickname_hint) @@ -57,95 +70,151 @@ class LoginFormFragment : BaseFragment(), LoginFormView { private lateinit var hostSymbols: Array - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_form, container, false) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginFormBinding.bind(view) + presenter.onAttachView(this) + initializeCaptchaResultObserver() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this) + private fun initializeCaptchaResultObserver() { + setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ -> + presenter.onRetryAfterCaptcha() + } } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(false) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } - loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } - loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginFormSignIn.setOnClickListener { presenter.onSignInClick() } - loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } - loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } - loginFormFaq.setOnClickListener { presenter.onFaqClick() } - loginFormContactEmail.setOnClickListener { presenter.onEmailClick() } - loginFormRecoverLink.setOnClickListener { presenter.onRecoverClick() } - loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + with(binding) { + loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } + loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } + loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() } + loginFormSignIn.setOnClickListener { presenter.onSignInClick() } + loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } + loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } + loginFormFaq.setOnClickListener { presenter.onFaqClick() } + loginFormContactEmail.setOnClickListener { presenter.onEmailClick() } + loginFormRecoverLink.setOnClickListener { presenter.onRecoverClick() } + loginFormRecoverLinkSecond.setOnClickListener { presenter.onRecoverClick() } + loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() } + loginFormHost.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) requireActivity().hideSoftInput() + } + } - with(loginFormHost) { + with(binding.loginFormHost) { setText(hostKeys.getOrNull(0).orEmpty()) - setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginFormContainer.visibility == GONE) dismissDropDown() } + setAdapter( + LoginSymbolAdapter( + context, + R.layout.support_simple_spinner_dropdown_item, + hostKeys + ) + ) + setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() } } } + override fun getHostsValues(): List = hostValues.toList() + override fun setCredentials(username: String, pass: String) { - loginFormUsername.setText(username) - loginFormPass.setText(pass) + with(binding) { + loginFormUsername.setText(username) + loginFormPass.setText(pass) + } + } + + override fun setHost(host: String) { + binding.loginFormHost.setText( + hostKeys.getOrNull(hostValues.indexOf(host)).orEmpty() + ) } override fun setUsernameLabel(label: String) { - loginFormUsernameLayout.hint = label + binding.loginFormUsernameLayout.hint = label } override fun setErrorUsernameRequired() { - with(loginFormUsernameLayout) { - requestFocus() - error = getString(R.string.login_field_required) + with(binding.loginFormUsernameLayout) { + error = getString(R.string.error_field_required) } } override fun setErrorLoginRequired() { - with(loginFormUsernameLayout) { - requestFocus() + with(binding.loginFormUsernameLayout) { error = getString(R.string.login_invalid_login) } } override fun setErrorEmailRequired() { - with(loginFormUsernameLayout) { - requestFocus() + with(binding.loginFormUsernameLayout) { error = getString(R.string.login_invalid_email) } } override fun setErrorPassRequired(focus: Boolean) { - with(loginFormPassLayout) { - if (focus) requestFocus() - error = getString(R.string.login_field_required) + with(binding.loginFormPassLayout) { + error = getString(R.string.error_field_required) + setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) } } override fun setErrorPassInvalid(focus: Boolean) { - with(loginFormPassLayout) { - if (focus) requestFocus() + with(binding.loginFormPassLayout) { error = getString(R.string.login_invalid_password) + setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) } } - override fun setErrorPassIncorrect() { - with(loginFormPassLayout) { - requestFocus() - error = getString(R.string.login_incorrect_password) + override fun setErrorPassIncorrect(message: String?) { + with(binding) { + loginFormUsernameLayout.error = " " + loginFormPassLayout.error = " " + loginFormPassLayout.setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) + loginFormHostLayout.error = " " + loginFormErrorBox.text = message ?: getString(R.string.login_incorrect_password_default) + loginFormErrorBox.isVisible = true + } + } + + override fun setErrorEmailInvalid(domain: String) { + with(binding.loginFormUsernameLayout) { + error = getString(R.string.login_invalid_custom_email, domain) + } + } + + override fun setDomainSuffixInvalid() { + with(binding.loginFormDomainSuffixLayout) { + error = getString(R.string.login_invalid_domain_suffix) } } override fun clearUsernameError() { - loginFormUsernameLayout.error = null + binding.loginFormUsernameLayout.error = null + binding.loginFormErrorBox.isVisible = false } override fun clearPassError() { - loginFormPassLayout.error = null + binding.loginFormPassLayout.error = null + binding.loginFormPassLayout.setEndIconTintList( + requireContext().getAttrColorStateList(R.attr.colorOnSurface) + ) + binding.loginFormErrorBox.isVisible = false + } + + override fun clearHostError() { + binding.loginFormHostLayout.error = null + binding.loginFormErrorBox.isVisible = false + } + + override fun clearDomainSuffixError() { + binding.loginFormDomainSuffixLayout.error = null } override fun showSoftKeyboard() { @@ -157,28 +226,56 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override fun showProgress(show: Boolean) { - loginFormProgress.visibility = if (show) VISIBLE else GONE + binding.loginFormProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginFormContainer.visibility = if (show) VISIBLE else GONE + binding.loginFormContainer.visibility = if (show) VISIBLE else GONE + } + + override fun showAdminMessage(message: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginFormMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(message) + binding.loginFormMessage.root.isVisible = message != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } + + override fun showDomainSuffixInput(show: Boolean) { + binding.loginFormDomainSuffixLayout.isVisible = show + } + + override fun showOtherOptionsButton(show: Boolean) { + binding.loginFormAdvancedButton.isVisible = show } @SuppressLint("SetTextI18n") override fun showVersion() { - loginFormVersion.text = "v${appInfo.versionName}" - } - - override fun notifyParentAccountLogged(students: List, loginData: Triple) { - (activity as? LoginActivity)?.onFormFragmentAccountLogged(students, loginData) - } - - override fun openPrivacyPolicyPage() { - context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage) + binding.loginFormVersion.text = "v${appInfo.versionName}" } override fun showContact(show: Boolean) { - loginFormContact.visibility = if (show) VISIBLE else GONE + binding.loginFormContact.isVisible = show + } + + override fun openPrivacyPolicyPage() { + context?.openInternetBrowser( + "https://wulkanowy.github.io/polityka-prywatnosci.html", + ::showMessage + ) + } + + override fun navigateToSymbol(loginData: LoginData) { + (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) + } + + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun openAdvancedLogin() { @@ -190,31 +287,24 @@ class LoginFormFragment : BaseFragment(), LoginFormView { } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } override fun openFaqPage() { - context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", ::showMessage) + context?.openInternetBrowser( + "https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", + ::showMessage + ) } override fun onResume() { super.onResume() presenter.updateUsernameLabel() + presenter.updateCustomDomainSuffixVisibility() } - override fun openEmail(lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString(R.string.login_email_text, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", - appInfo.systemVersion.toString(), - appInfo.versionName, - "$formHostValue/$formHostSymbol", - lastError - ) - ) + override fun openEmail(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index 997e1e85..39bc3f02 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -1,20 +1,39 @@ package io.github.wulkanowy.ui.modules.login.form -import io.github.wulkanowy.data.repositories.student.StudentRepository +import androidx.core.net.toUri +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceLoading +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.ifNullOrBlank import timber.log.Timber +import java.net.URL import javax.inject.Inject class LoginFormPresenter @Inject constructor( - schedulers: SchedulersProvider, studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(loginErrorHandler, studentRepository, schedulers) { + private val appInfo: AppInfo, + private val analytics: AnalyticsHelper, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, + private val preferencesRepository: PreferencesRepository, +) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null @@ -23,14 +42,40 @@ class LoginFormPresenter @Inject constructor( view.run { initView() showContact(false) + showOtherOptionsButton(appInfo.isDebug) showVersion() loginErrorHandler.onBadCredentials = { - setErrorPassIncorrect() + setErrorPassIncorrect(it.takeIf { !it.isNullOrBlank() }) showSoftKeyboard() Timber.i("Entered wrong username or password") } } + + reloadAdminMessage() + } + + private fun reloadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = view?.formHostValue.orEmpty(), + type = MessageType.LOGIN_MESSAGE, + ) + } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch() + } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) } fun onPrivacyLinkClick() { @@ -45,16 +90,33 @@ class LoginFormPresenter @Inject constructor( view?.apply { clearPassError() clearUsernameError() - if (formHostValue.contains("fakelog")) { + clearHostError() + if (formHostValue.contains("wulkanowy")) { setCredentials("jan@fakelog.cf", "jan123") + } else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") { + setCredentials("", "") } + updateCustomDomainSuffixVisibility() updateUsernameLabel() + reloadAdminMessage() + } + } + + fun onDomainSuffixChanged() { + view?.apply { + clearDomainSuffixError() + } + } + + fun updateCustomDomainSuffixVisibility() { + view?.run { + showDomainSuffixInput("customSuffix" in formHostValue) } } fun updateUsernameLabel() { view?.run { - setUsernameLabel(if ("standard" in formHostValue) emailLabel else nicknameLabel) + setUsernameLabel(if ("email" !in formHostValue) nicknameLabel else emailLabel) } } @@ -64,44 +126,98 @@ class LoginFormPresenter @Inject constructor( fun onUsernameTextChanged() { view?.clearUsernameError() + + val username = view?.formUsernameValue.orEmpty().trim() + if ("@" in username && "@vulcan" !in username) { + val hosts = view?.getHostsValues().orEmpty().associateBy { it.toUri().host } + val usernameHost = username.substringAfter("@") + + hosts[usernameHost]?.let { + view?.run { + setHost(it) + clearHostError() + } + } + } } - fun onSignInClick() { + private fun getLoginData(): LoginData { val email = view?.formUsernameValue.orEmpty().trim() val password = view?.formPassValue.orEmpty().trim() val host = view?.formHostValue.orEmpty().trim() + val domainSuffix = view?.formDomainSuffix.orEmpty().trim().takeIf { + "customSuffix" in host + }.orEmpty() val symbol = view?.formHostSymbol.orEmpty().trim() - if (!validateCredentials(email, password, host)) return + return LoginData( + login = email, + password = password, + baseUrl = host, + domainSuffix = domainSuffix, + defaultSymbol = symbol + ) + } - disposable.add(studentRepository.getStudentsScrapper(email, password, host, symbol) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { + fun onRetryAfterCaptcha() { + onSignInClick() + } + + fun onSignInClick() { + val loginData = getLoginData() + + if (!validateCredentials(loginData)) return + + resourceFlow { + studentRepository.getUserSubjectsFromScrapper( + email = loginData.login, + password = loginData.password, + scrapperBaseUrl = loginData.baseUrl, + domainSuffix = loginData.domainSuffix, + symbol = loginData.defaultSymbol, + ) + } + .logResourceStatus("login") + .onResourceLoading { + view?.run { hideSoftKeyboard() showProgress(true) showContent(false) } - Timber.i("Login started") } - .doFinally { + .onResourceSuccess { + when (it.symbols.size) { + 0 -> view?.navigateToSymbol(loginData) + else -> view?.navigateToStudentSelect(loginData, it) + } + analytics.logEvent( + "registration_form", + "success" to true, + "scrapperBaseUrl" to loginData.baseUrl, + "error" to "No error" + ) + } + .onResourceNotLoading { view?.apply { showProgress(false) showContent(true) } } - .subscribe({ - Timber.i("Login result: Success") - analytics.logEvent("registration_form", "success" to true, "students" to it.size, "scrapperBaseUrl" to host, "error" to "No error") - view?.notifyParentAccountLogged(it, Triple(email, password, host)) - }, { - Timber.i("Login result: An exception occurred") - analytics.logEvent("registration_form", "success" to false, "students" to -1, "scrapperBaseUrl" to host, "error" to it.message.ifNullOrBlank { "No message" }) + .onResourceError { loginErrorHandler.dispatch(it) + if (it is InvalidSymbolException) { + loginErrorHandler.showDefaultMessage(it) + } lastError = it view?.showContact(true) - })) + analytics.logEvent( + "registration_form", + "success" to false, + "scrapperBaseUrl" to loginData.baseUrl, + "error" to it.message.ifNullOrBlank { "No message" } + ) + } + .launch("login") } fun onFaqClick() { @@ -109,41 +225,65 @@ class LoginFormPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(lastError?.message.ifNullOrBlank { "none" }) + view?.openEmail( + LoginSupportInfo( + loginData = getLoginData(), + lastErrorMessage = lastError?.message, + registerUser = null, + enteredSymbol = null, + ) + ) } fun onRecoverClick() { view?.onRecoverClick() } - private fun validateCredentials(login: String, password: String, host: String): Boolean { + private fun validateCredentials(loginData: LoginData): Boolean { var isCorrect = true - if (login.isEmpty()) { + if (loginData.login.isEmpty()) { view?.setErrorUsernameRequired() isCorrect = false } else { - if ("@" in login && "standard" !in host) { + if ("@" in loginData.login && "login" in loginData.baseUrl) { view?.setErrorLoginRequired() isCorrect = false } - - if ("@" !in login && "standard" in host) { + if ("@" !in loginData.login && "email" in loginData.baseUrl) { view?.setErrorEmailRequired() isCorrect = false } + + val isEmailLogin = "@" in loginData.login + val isEmailWithLogin = "||" !in loginData.login + val isLoginNotRequired = "login" !in loginData.baseUrl + val isEmailNotRequired = "email" !in loginData.baseUrl + if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) { + val emailHost = loginData.login.substringAfter("@") + val emailDomain = URL(loginData.baseUrl).host + if (!emailHost.equals(emailDomain, true)) { + view?.setErrorEmailInvalid(domain = emailDomain) + isCorrect = false + } + } } - if (password.isEmpty()) { + if (loginData.password.isEmpty()) { view?.setErrorPassRequired(focus = isCorrect) isCorrect = false } - if (password.length < 6 && password.isNotEmpty()) { + if (loginData.password.length < 6 && loginData.password.isNotEmpty()) { view?.setErrorPassInvalid(focus = isCorrect) isCorrect = false } + if (loginData.domainSuffix !in listOf("", "rc", "kurs")) { + view?.setDomainSuffixInvalid() + isCorrect = false + } + return isCorrect } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt index 6506fa88..6ea22d18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt @@ -1,7 +1,10 @@ package io.github.wulkanowy.ui.modules.login.form -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginFormView : BaseView { @@ -13,14 +16,20 @@ interface LoginFormView : BaseView { val formHostValue: String + val formDomainSuffix: String + val formHostSymbol: String val nicknameLabel: String val emailLabel: String + fun getHostsValues(): List + fun setCredentials(username: String, pass: String) + fun setHost(host: String) + fun setUsernameLabel(label: String) fun setErrorUsernameRequired() @@ -33,12 +42,20 @@ interface LoginFormView : BaseView { fun setErrorPassInvalid(focus: Boolean) - fun setErrorPassIncorrect() + fun setErrorPassIncorrect(message: String?) + + fun setErrorEmailInvalid(domain: String) + + fun setDomainSuffixInvalid() fun clearUsernameError() fun clearPassError() + fun clearHostError() + + fun clearDomainSuffixError() + fun showSoftKeyboard() fun hideSoftKeyboard() @@ -47,9 +64,19 @@ interface LoginFormView : BaseView { fun showContent(show: Boolean) + fun showAdminMessage(message: AdminMessage?) + + fun openInternetBrowser(url: String) + + fun showDomainSuffixInput(show: Boolean) + + fun showOtherOptionsButton(show: Boolean) + fun showVersion() - fun notifyParentAccountLogged(students: List, loginData: Triple) + fun navigateToSymbol(loginData: LoginData) + + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun openPrivacyPolicyPage() @@ -57,7 +84,7 @@ interface LoginFormView : BaseView { fun openFaqPage() - fun openEmail(lastError: String) + fun openEmail(supportInfo: LoginSupportInfo) fun openAdvancedLogin() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 1bff4926..b9afba98 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -3,29 +3,41 @@ package io.github.wulkanowy.ui.modules.login.recover import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import android.webkit.JavascriptInterface -import android.webkit.WebView -import android.webkit.WebViewClient +import android.webkit.* +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import com.yariksoffice.lingver.Lingver +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentLoginRecoverBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.form.LoginSymbolAdapter import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_recover.* import javax.inject.Inject -class LoginRecoverFragment : BaseFragment(), LoginRecoverView { +@AndroidEntryPoint +class LoginRecoverFragment : + BaseFragment(R.layout.fragment_login_recover), LoginRecoverView { + + private var _binding: FragmentLoginRecoverBinding? = null + + private val bindingLocal: FragmentLoginRecoverBinding get() = _binding!! @Inject lateinit var presenter: LoginRecoverPresenter + @Inject + lateinit var lingver: Lingver + + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { fun newInstance() = LoginRecoverFragment() } @@ -37,13 +49,15 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { private lateinit var hostSymbols: Array override val recoverHostValue: String - get() = hostValues.getOrNull(hostKeys.indexOf(loginRecoverHost.text.toString())).orEmpty() + get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())) + .orEmpty() override val formHostSymbol: String - get() = hostSymbols.getOrNull(hostKeys.indexOf(loginRecoverHost.text.toString())).orEmpty() + get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())) + .orEmpty() override val recoverNameValue: String - get() = loginRecoverName.text.toString().trim() + get() = bindingLocal.loginRecoverName.text.toString().trim() override val emailHintString: String get() = getString(R.string.login_email_hint) @@ -54,91 +68,105 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { override val invalidEmailString: String get() = getString(R.string.login_invalid_email) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_recover, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + restoreCorrectLocale() + _binding = FragmentLoginRecoverBinding.bind(view) presenter.onAttachView(this) } + // https://issuetracker.google.com/issues/37113860 + private fun restoreCorrectLocale() { + if (preferencesRepository.appLanguage == "system") { + lingver.setFollowSystemLocale(requireContext()) + } else { + lingver.setLocale(requireContext(), lingver.getLocale()) + } + } + override fun initView() { - loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT) + (requireActivity() as LoginActivity).showActionBar(true) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) - loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() } - loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } - loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } - loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } - loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } - loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) } + with(bindingLocal) { + loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT) + loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() } + loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } + loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } + loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } + loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() } + } - with(loginRecoverHost) { + with(bindingLocal.loginRecoverHost) { setText(hostKeys.getOrNull(0).orEmpty()) - setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) - setOnClickListener { if (loginRecoverFormContainer.visibility == GONE) dismissDropDown() } + setAdapter( + LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys) + ) + setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } } } override fun setDefaultCredentials(username: String) { - loginRecoverName.setText(username) + bindingLocal.loginRecoverName.setText(username) } override fun setErrorNameRequired() { - with(loginRecoverNameLayout) { + with(bindingLocal.loginRecoverNameLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.error_field_required) } } override fun setUsernameHint(hint: String) { - loginRecoverNameLayout.hint = hint + bindingLocal.loginRecoverNameLayout.hint = hint } override fun setUsernameError(message: String) { - with(loginRecoverNameLayout) { + with(bindingLocal.loginRecoverNameLayout) { requestFocus() error = message } } override fun clearUsernameError() { - loginRecoverNameLayout.error = null + bindingLocal.loginRecoverNameLayout.error = null } override fun showProgress(show: Boolean) { - loginRecoverProgress.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverProgress.visibility = if (show) VISIBLE else GONE } override fun showRecoverForm(show: Boolean) { - loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE } override fun showCaptcha(show: Boolean) { - loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - loginRecoverError.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverErrorDetails.isVisible = true } override fun setErrorDetails(message: String) { - loginRecoverErrorMessage.text = message + bindingLocal.loginRecoverErrorMessage.text = message } override fun showSuccessView(show: Boolean) { - loginRecoverSuccess.visibility = if (show) VISIBLE else GONE + bindingLocal.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE } override fun setSuccessTitle(title: String) { - loginRecoverSuccessTitle.text = title + bindingLocal.loginRecoverSuccessTitle.text = title } override fun setSuccessMessage(message: String) { - loginRecoverSuccessMessage.text = message + bindingLocal.loginRecoverSuccessMessage.text = message } override fun showSoftKeyboard() { @@ -159,10 +187,10 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { callback:e =>Android.captchaCallback(e)}) """.trimIndent() - with(loginRecoverWebView) { + with(bindingLocal.loginRecoverWebView) { settings.javaScriptEnabled = true webViewClient = object : WebViewClient() { - private var recoverWebViewSuccess: Boolean = true + private var recoverWebViewSuccess = true override fun onPageFinished(view: WebView?, url: String?) { if (recoverWebViewSuccess) { @@ -171,16 +199,23 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { } else { showProgress(false) showErrorView(true) + bindingLocal.loginRecoverErrorDetails.isVisible = false } } - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError? + ) { recoverWebViewSuccess = false } } loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) addJavascriptInterface(object { + + @Suppress("UNUSED") @JavascriptInterface fun captchaCallback(reCaptchaResponse: String) { activity?.runOnUiThread { @@ -197,8 +232,10 @@ class LoginRecoverFragment : BaseFragment(), LoginRecoverView { } override fun onDestroyView() { - super.onDestroyView() - loginRecoverWebView.destroy() + bindingLocal.loginRecoverWebView.destroy() + _binding = null presenter.onDetachView() + + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 9a21fb91..18902e01 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -1,21 +1,23 @@ package io.github.wulkanowy.ui.modules.login.recover -import io.github.wulkanowy.data.repositories.recover.RecoverRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.repositories.RecoverRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.ifNullOrBlank +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject class LoginRecoverPresenter @Inject constructor( - schedulers: SchedulersProvider, studentRepository: StudentRepository, private val loginErrorHandler: RecoverErrorHandler, - private val analytics: FirebaseAnalyticsHelper, + private val analytics: AnalyticsHelper, private val recoverRepository: RecoverRepository -) : BasePresenter(loginErrorHandler, studentRepository, schedulers) { +) : BasePresenter(loginErrorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -36,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor( fun onHostSelected() { view?.run { - if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf") + if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf") clearUsernameError() updateFields() } @@ -44,7 +46,7 @@ class LoginRecoverPresenter @Inject constructor( fun updateFields() { view?.run { - setUsernameHint(if ("standard" in recoverHostValue) emailHintString else loginPeselEmailHintString) + setUsernameHint(if ("email" in recoverHostValue) emailHintString else loginPeselEmailHintString) } } @@ -55,24 +57,31 @@ class LoginRecoverPresenter @Inject constructor( if (!validateInput(username, host)) return - disposable.add(recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { + resourceFlow { + recoverRepository.getReCaptchaSiteKey( + host, + symbol.ifBlank { "default" }) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { hideSoftKeyboard() showRecoverForm(false) showProgress(true) showErrorView(false) showCaptcha(false) } + is Resource.Success -> view?.run { + loadReCaptcha(url = it.data.first, siteKey = it.data.second) + showProgress(false) + showErrorView(false) + showCaptcha(true) + } + is Resource.Error -> { + Timber.i("Obtain captcha site key result: An exception occurred") + errorHandler.dispatch(it.error) + } } - .subscribe({ (resetUrl, siteKey) -> - view?.loadReCaptcha(siteKey, resetUrl) - }) { - Timber.i("Obtain captcha site key result: An exception occurred") - errorHandler.dispatch(it) - }) + }.launch("captcha") } private fun validateInput(username: String, host: String): Boolean { @@ -83,7 +92,7 @@ class LoginRecoverPresenter @Inject constructor( isCorrect = false } - if ("standard" in host && "@" !in username) { + if ("email" in host && "@" !in username) { view?.setUsernameError(view?.invalidEmailString.orEmpty()) isCorrect = false } @@ -94,37 +103,47 @@ class LoginRecoverPresenter @Inject constructor( fun onReCaptchaVerified(reCaptchaResponse: String) { val username = view?.recoverNameValue.orEmpty() val host = view?.recoverHostValue.orEmpty() - val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } + val symbol = view?.formHostSymbol.ifNullOrBlank { "default" } - with(disposable) { - clear() - add(recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { - showProgress(true) - showRecoverForm(false) - showCaptcha(false) - } + resourceFlow { + recoverRepository.sendRecoverRequest( + host, + symbol, + username, + reCaptchaResponse + ) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { + showProgress(true) + showRecoverForm(false) + showCaptcha(false) } - .doFinally { - view?.showProgress(false) + is Resource.Success -> view?.run { + showSuccessView(true) + setSuccessTitle(it.data.substringBefore(". ")) + setSuccessMessage(it.data.substringAfter(". ")) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to true + ) } - .subscribe({ - view?.run { - showSuccessView(true) - setSuccessTitle(it.substringBefore(". ")) - setSuccessMessage(it.substringAfter(". ")) - } - - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to true) - }) { + is Resource.Error -> { Timber.i("Send recover request result: An exception occurred") - errorHandler.dispatch(it) - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to false) - }) - } + errorHandler.dispatch(it.error) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to false + ) + } + } + }.onResourceNotLoading { + view?.showProgress(false) + }.launch("verified") } fun onDetailsClick() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt index 110dd82e..28686d62 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.login.recover -import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.sdk.scrapper.exception.InvalidCaptchaException import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException @@ -9,9 +9,8 @@ import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject class RecoverErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { + @ApplicationContext context: Context, +) : ErrorHandler(context) { var onInvalidUsername: (String) -> Unit = {} @@ -19,7 +18,8 @@ class RecoverErrorHandler @Inject constructor( override fun proceed(error: Throwable) { when (error) { - is InvalidEmailException, is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty()) + is InvalidEmailException, + is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty()) is InvalidCaptchaException -> onInvalidCaptcha(error.localizedMessage.orEmpty(), error) else -> super.proceed(error) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt new file mode 100644 index 00000000..ef8cf4ee --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -0,0 +1,191 @@ +package io.github.wulkanowy.ui.modules.login.studentselect + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil.ItemCallback +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.* +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class LoginStudentSelectAdapter @Inject constructor() : + ListAdapter(Differ) { + + override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (LoginStudentSelectItemType.entries[viewType]) { + LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder( + ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false), + ) + + LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder( + ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false) + ) + + LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder( + ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false) + ) + + LoginStudentSelectItemType.STUDENT -> StudentViewHolder( + ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false) + ) + + LoginStudentSelectItemType.HELP -> HelpViewHolder( + ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false) + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader) + is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader) + is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader) + is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student) + is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help) + } + } + + private class EmptySymbolsHeaderViewHolder( + private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) { + with(binding) { + loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f + root.setOnClickListener { item.onClick() } + } + } + } + + private class SymbolsHeaderViewHolder( + private val binding: ItemLoginStudentSelectHeaderSymbolBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.SymbolHeader) { + with(binding) { + loginStudentSelectHeaderSymbolValue.text = buildString { + append(root.context.getString(R.string.mobile_device_symbol)) + append(": ") + append(item.humanReadableName ?: item.symbol.symbol) + if (!item.humanReadableName.isNullOrBlank()) { + append(" (${item.symbol.symbol})") + } + } + loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName + loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank() + loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message + loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null + loginStudentSelectHeaderSymbolError.maxLines = when { + item.isErrorExpanded -> Int.MAX_VALUE + else -> 2 + } + + if (item.symbol.error != null) { + root.setOnClickListener { item.onClick(item.symbol) } + } else root.setOnClickListener(null) + } + } + } + + private class SchoolHeaderViewHolder( + private val binding: ItemLoginStudentSelectHeaderSchoolBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.SchoolHeader) { + with(binding) { + loginStudentSelectHeaderSchoolName.text = buildString { + append(item.unit.schoolName.trim()) + if (item.unit.schoolShortName.isNotBlank()) { + append(" (") + append(item.unit.schoolShortName) + append(")") + } + } + loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty() + loginStudentSelectHeaderSchoolError.text = item.unit.error?.message + loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null + loginStudentSelectHeaderSchoolError.maxLines = when { + item.isErrorExpanded -> Int.MAX_VALUE + else -> 2 + } + + if (item.unit.error != null) { + root.setOnClickListener { item.onClick(item.unit) } + } else root.setOnClickListener(null) + } + } + } + + private class StudentViewHolder( + private val binding: ItemLoginStudentSelectStudentBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.Student) { + val student = item.student + val semesters = student.semesters + val diary = semesters.maxByOrNull { it.semesterId } + + with(binding) { + loginItemName.text = "${student.studentName} ${student.studentSurname}" + loginItemName.isEnabled = item.isEnabled + loginItemSignedIn.text = if (!item.isEnabled) { + root.context.getString(R.string.login_signed_in) + } else diary?.diaryName + + with(loginItemCheck) { + keyListener = null + isEnabled = item.isEnabled + isChecked = item.isSelected || !item.isEnabled + } + + root.isEnabled = item.isEnabled + root.setOnClickListener { + item.onClick(item) + } + } + } + } + + private class HelpViewHolder( + private val binding: ItemLoginStudentSelectHelpBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.Help) { + with(binding) { + loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible + loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() } + loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() } + loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() } + } + } + } + + private object Differ : ItemCallback() { + + override fun areItemsTheSame( + oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem + ): Boolean = when { + oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true + oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> { + oldItem.symbol == newItem.symbol + } + + oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> { + oldItem.student == newItem.student + } + + else -> oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem + ): Boolean = oldItem == newItem + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 9860af22..4eb60265 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -1,117 +1,133 @@ package io.github.wulkanowy.ui.modules.login.studentselect import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.core.os.bundleOf +import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder +import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_login_student_select.* -import java.io.Serializable +import io.github.wulkanowy.utils.serializable import javax.inject.Inject -class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView { +@AndroidEntryPoint +class LoginStudentSelectFragment : + BaseFragment(R.layout.fragment_login_student_select), + LoginStudentSelectView { @Inject lateinit var presenter: LoginStudentSelectPresenter @Inject - lateinit var loginAdapter: FlexibleAdapter> + lateinit var loginAdapter: LoginStudentSelectAdapter @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + + private lateinit var symbolsNames: Array + private lateinit var symbolsValues: Array + + override val symbols: Map by lazy { + symbolsValues.zip(symbolsNames).toMap() + } + companion object { - const val SAVED_STUDENTS = "STUDENTS" + private const val ARG_LOGIN = "LOGIN" + private const val ARG_STUDENTS = "STUDENTS" - fun newInstance() = LoginStudentSelectFragment() + fun newInstance(loginData: LoginData, registerUser: RegisterUser) = + LoginStudentSelectFragment().apply { + arguments = bundleOf( + ARG_LOGIN to loginData, + ARG_STUDENTS to registerUser, + ) + } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_student_select, container, false) - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginStudentSelectBinding.bind(view) - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_STUDENTS)) + symbolsNames = resources.getStringArray(R.array.symbols) + symbolsValues = resources.getStringArray(R.array.symbols_values) + + presenter.onAttachView( + view = this, + loginData = requireArguments().serializable(ARG_LOGIN), + registerUser = requireArguments().serializable(ARG_STUDENTS), + ) } override fun initView() { - loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } - loginAdapter.apply { setOnItemClickListener { presenter.onItemSelected(it) } } - loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } - loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } + (requireActivity() as LoginActivity).showActionBar(true) - loginStudentSelectRecycler.apply { - adapter = loginAdapter - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding) { + loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } + loginStudentSelectRecycler.adapter = loginAdapter } } override fun updateData(data: List) { - loginAdapter.updateDataSet(data) + loginAdapter.submitList(data) } - override fun openMainView() { - activity?.let { startActivity(MainActivity.getStartIntent(context = it, clear = true)) } + override fun navigateToSymbol(loginData: LoginData) { + (requireActivity() as LoginActivity).navigateToSymbolFragment(loginData) + } + + override fun navigateToNext() { + (requireActivity() as LoginActivity).navigateToNotifications() } override fun showProgress(show: Boolean) { - loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectProgress.isVisible = show } override fun showContent(show: Boolean) { - loginStudentSelectContent.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectContent.isVisible = show } override fun enableSignIn(enable: Boolean) { - loginStudentSelectSignIn.isEnabled = enable - } - - fun onParentInitStudentSelectFragment(students: List) { - presenter.onParentInitStudentSelectView(students) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putSerializable(SAVED_STUDENTS, presenter.students as Serializable) - } - - override fun showContact(show: Boolean) { - loginStudentSelectContact.visibility = if (show) VISIBLE else GONE - } - - override fun onDestroyView() { - presenter.onDetachView() - super.onDestroyView() + binding.loginStudentSelectSignIn.isEnabled = enable } override fun openDiscordInvite() { context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) } - override fun openEmail(lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString(R.string.login_email_text, appInfo.systemModel, - appInfo.systemVersion.toString(), - appInfo.versionName, - "Select users to log in", - lastError - ) - ) + override fun openEmail(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") + } + + override fun showAdminMessage(adminMessage: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginStudentSelectAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessage) + binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt index 06be6138..1edc8e7b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt @@ -1,71 +1,50 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import android.annotation.SuppressLint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_login_student_select.* +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit -class LoginStudentSelectItem(val student: Student, val alreadySaved: Boolean) : - AbstractFlexibleItem() { +sealed class LoginStudentSelectItem(val type: LoginStudentSelectItemType) { - override fun getLayoutRes() = R.layout.item_login_student_select + data class EmptySymbolsHeader( + val isExpanded: Boolean, + val onClick: () -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER) - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ItemViewHolder { - return ItemViewHolder(view, adapter) - } + data class SymbolHeader( + val symbol: RegisterSymbol, + val humanReadableName: String?, + val isErrorExpanded: Boolean, + val onClick: (RegisterSymbol) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.SYMBOL_HEADER) - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ItemViewHolder, position: Int, payloads: MutableList) { - holder.apply { - loginItemName.text = "${student.studentName} ${student.className}" - loginItemSchool.text = student.schoolName - loginItemName.isEnabled = !alreadySaved - loginItemSchool.isEnabled = !alreadySaved - loginItemCheck.isEnabled = !alreadySaved - loginItemSignedIn.visibility = if (alreadySaved) VISIBLE else GONE - } - } + data class SchoolHeader( + val unit: RegisterUnit, + val isErrorExpanded: Boolean, + val onClick: (RegisterUnit) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.SCHOOL_HEADER) - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false + data class Student( + val symbol: RegisterSymbol, + val unit: RegisterUnit, + val student: RegisterStudent, + val isEnabled: Boolean, + val isSelected: Boolean, + val onClick: (Student) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.STUDENT) - other as LoginStudentSelectItem - - if (student != other.student) return false - if (alreadySaved != other.alreadySaved) return false - - return true - } - - override fun hashCode(): Int { - return student.hashCode() - } - - class ItemViewHolder(view: View, val adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - - override val containerView: View - get() = itemView - - init { - loginItemCheck.keyListener = null - } - - override fun onClick(view: View?) { - super.onClick(view) - - if (loginItemCheck.isEnabled) { - loginItemCheck.apply { isChecked = !isChecked } - } - } - } + data class Help( + val onEnterSymbolClick: () -> Unit, + val onContactUsClick: () -> Unit, + val onDiscordClick: () -> Unit, + val isSymbolButtonVisible: Boolean, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.HELP) +} + +enum class LoginStudentSelectItemType { + EMPTY_SYMBOLS_HEADER, + SYMBOL_HEADER, + SCHOOL_HEADER, + STUDENT, + HELP, } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 82de5a88..39070cf0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -1,35 +1,69 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SchoolsRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase +import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException +import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.ifNullOrBlank +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.isCurrent +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.io.Serializable import javax.inject.Inject class LoginStudentSelectPresenter @Inject constructor( - schedulers: SchedulersProvider, studentRepository: StudentRepository, + private val schoolsRepository: SchoolsRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(loginErrorHandler, studentRepository, schedulers) { + private val syncManager: SyncManager, + private val analytics: AnalyticsHelper, + private val appInfo: AppInfo, + private val preferencesRepository: PreferencesRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase +) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null - var students = emptyList() + private lateinit var registerUser: RegisterUser + private lateinit var loginData: LoginData - private var selectedStudents = mutableListOf() + private lateinit var students: List + private var isEmptySymbolsExpanded = false + private var expandedSymbolError: RegisterSymbol? = null + private var expandedSchoolError: RegisterUnit? = null - fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { + private val selectedStudents = mutableListOf() + + fun onAttachView( + view: LoginStudentSelectView, + loginData: LoginData, + registerUser: RegisterUser, + ) { super.onAttachView(view) - view.run { + with(view) { initView() - showContact(false) enableSignIn(false) loginErrorHandler.onStudentDuplicate = { showMessage(it) @@ -37,94 +71,307 @@ class LoginStudentSelectPresenter @Inject constructor( } } - if (students is List<*> && students.isNotEmpty()) { - loadData(students.filterIsInstance()) + this.loginData = loginData + this.registerUser = registerUser + loadData() + loadAdminMessage() + } + + private fun loadData() { + resetSelectedState() + + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + students = it.dataOrNull.orEmpty() + when (it) { + is Resource.Loading -> Timber.d("Login student select students load started") + is Resource.Success -> { + getStudentsWithCurrentlyActiveSemesters() + selectedStudents.clear() + selectedStudents.addAll(getStudentsWithCurrentlyActiveSemesters()) + view?.enableSignIn(selectedStudents.isNotEmpty()) + refreshItems() + } + + is Resource.Error -> { + errorHandler.dispatch(it.error) + lastError = it.error + refreshItems() + } + } + }.launch("load_data") + } + + private fun loadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = registerUser.scrapperBaseUrl.orEmpty(), + type = MessageType.LOGIN_STUDENT_SELECT_MESSAGE, + ) + } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch("load_admin_message") + } + + private fun getStudentsWithCurrentlyActiveSemesters(): List { + val students = registerUser.symbols.flatMap { symbol -> + symbol.schools.flatMap { unit -> + unit.students.map { + createStudentItem(it, symbol, unit, students) + } + } + } + return students + .filter { it.isEnabled } + .filter { student -> + student.student.semesters.any { semester -> + semester.isCurrent() + } + } + } + + private fun createItems(): List = buildList { + val notEmptySymbols = registerUser.symbols.filter { it.shouldShowOnTop() } + val emptySymbols = registerUser.symbols.filter { !it.shouldShowOnTop() } + + if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) { + add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol })) + } + + addAll(createNotEmptySymbolItems(notEmptySymbols, students)) + addAll(createEmptySymbolItems(emptySymbols, notEmptySymbols.isNotEmpty())) + + val helpItem = LoginStudentSelectItem.Help( + onEnterSymbolClick = ::onEnterSymbol, + onContactUsClick = ::onEmailClick, + onDiscordClick = ::onDiscordClick, + isSymbolButtonVisible = "login" !in loginData.baseUrl, + ) + add(helpItem) + } + + private fun RegisterSymbol.shouldShowOnTop(): Boolean { + return schools.isNotEmpty() || error is StudentGraduateException + } + + private fun createNotEmptySymbolItems( + notEmptySymbols: List, + students: List, + ) = buildList { + notEmptySymbols.forEach { registerSymbol -> + val symbolHeader = LoginStudentSelectItem.SymbolHeader( + symbol = registerSymbol, + humanReadableName = view?.symbols?.get(registerSymbol.symbol), + isErrorExpanded = expandedSymbolError == registerSymbol, + onClick = ::onSymbolItemClick, + ) + add(symbolHeader) + + registerSymbol.schools.forEach { registerUnit -> + val schoolHeader = LoginStudentSelectItem.SchoolHeader( + unit = registerUnit, + isErrorExpanded = expandedSchoolError == registerUnit, + onClick = ::onUnitItemClick, + ) + add(schoolHeader) + + registerUnit.students.forEach { + add(createStudentItem(it, registerSymbol, registerUnit, students)) + } + } } } + private fun createStudentItem( + student: RegisterStudent, + symbol: RegisterSymbol, + school: RegisterUnit, + students: List, + ) = LoginStudentSelectItem.Student( + symbol = symbol, + unit = school, + student = student, + onClick = ::onItemSelected, + isEnabled = students.none { + it.student.email == registerUser.login + && it.student.symbol == symbol.symbol + && it.student.studentId == student.studentId + && it.student.schoolSymbol == school.schoolId + && it.student.classId == student.classId + }, + isSelected = selectedStudents + .filter { it.symbol.symbol == symbol.symbol } + .filter { it.unit.schoolId == school.schoolId } + .filter { it.student.studentId == student.studentId } + .filter { it.student.classId == student.classId } + .size == 1, + ) + + private fun createEmptySymbolItems( + emptySymbols: List, + isNotEmptySymbolsExist: Boolean, + ) = buildList { + val filteredEmptySymbols = emptySymbols.filter { + it.error !is InvalidSymbolException + }.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() } + + if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) { + val emptyHeader = LoginStudentSelectItem.EmptySymbolsHeader( + isExpanded = isEmptySymbolsExpanded, + onClick = ::onEmptySymbolsToggle, + ) + add(emptyHeader) + if (isEmptySymbolsExpanded) { + filteredEmptySymbols.forEach { + add(createEmptySymbolItem(it)) + } + } + } + + if (filteredEmptySymbols.isNotEmpty() && !isNotEmptySymbolsExist) { + filteredEmptySymbols.forEach { + add(createEmptySymbolItem(it)) + } + } + } + + private fun createEmptySymbolItem(registerSymbol: RegisterSymbol) = + LoginStudentSelectItem.SymbolHeader( + symbol = registerSymbol, + humanReadableName = view?.symbols?.get(registerSymbol.symbol), + isErrorExpanded = expandedSymbolError == registerSymbol, + onClick = ::onSymbolItemClick, + ) + fun onSignIn() { registerStudents(selectedStudents) } - fun onParentInitStudentSelectView(students: List) { - loadData(students) - if (students.size == 1) registerStudents(students) + private fun onEmptySymbolsToggle() { + isEmptySymbolsExpanded = !isEmptySymbolsExpanded + + refreshItems() } - fun onItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is LoginStudentSelectItem && !item.alreadySaved) { - selectedStudents.removeAll { it == item.student } - .let { if (!it) selectedStudents.add(item.student) } + private fun onItemSelected(item: LoginStudentSelectItem.Student) { + if (!item.isEnabled) return - view?.enableSignIn(selectedStudents.isNotEmpty()) + selectedStudents + .removeAll { + it.student.studentId == item.student.studentId && + it.student.classId == item.student.classId && + it.unit.schoolId == item.unit.schoolId && + it.symbol.symbol == item.symbol.symbol + } + .let { if (!it) selectedStudents.add(item) } + + view?.enableSignIn(selectedStudents.isNotEmpty()) + refreshItems() + } + + private fun onSymbolItemClick(symbol: RegisterSymbol) { + expandedSymbolError = if (symbol != expandedSymbolError) symbol else null + refreshItems() + } + + private fun onUnitItemClick(unit: RegisterUnit) { + expandedSchoolError = if (unit != expandedSchoolError) unit else null + refreshItems() + } + + private fun resetSelectedState() { + selectedStudents.clear() + view?.enableSignIn(false) + } + + private fun refreshItems() { + view?.updateData(createItems()) + } + + private fun registerStudents(students: List) { + val filteredStudents = students.filterIsInstance() + val studentsWithSemesters = filteredStudents.map { item -> + item.student.mapToStudentWithSemesters( + user = registerUser, + symbol = item.symbol, + scrapperDomainSuffix = loginData.domainSuffix, + unit = item.unit, + colors = appInfo.defaultColorsForAvatar, + ) } - } + resourceFlow { + studentRepository.saveStudents(studentsWithSemesters) + schoolsRepository.logSchoolLogin(loginData, studentsWithSemesters) + } + .logResourceStatus("registration") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + showProgress(true) + showContent(false) + } - private fun compareStudents(a: Student, b: Student): Boolean { - return a.email == b.email - && a.symbol == b.symbol - && a.studentId == b.studentId - && a.schoolSymbol == b.schoolSymbol - && a.classId == b.classId - } + is Resource.Success -> { + syncManager.startOneTimeSyncWorker(quiet = true) + view?.navigateToNext() + logRegisterEvent(studentsWithSemesters) + } - private fun loadData(students: List) { - this.students = students - disposable.add(studentRepository.getSavedStudents() - .map { savedStudents -> - students.map { student -> - Pair(student, savedStudents.any { compareStudents(student, it) }) + is Resource.Error -> { + view?.apply { + showProgress(false) + showContent(true) + } + lastError = it.error + loginErrorHandler.dispatch(it.error) + logRegisterEvent(studentsWithSemesters, it.error) + } } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - view?.updateData(it.map { studentPair -> - LoginStudentSelectItem(studentPair.first, studentPair.second) - }) - }, { - errorHandler.dispatch(it) - lastError = it - view?.updateData(students.map { student -> LoginStudentSelectItem(student, false) }) - }) - ) + }.launch("register") } - private fun registerStudents(students: List) { - disposable.add(studentRepository.saveStudents(students) - .map { students.first().apply { id = it.first() } } - .flatMapCompletable { studentRepository.switchStudent(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - showProgress(true) - showContent(false) - } - Timber.i("Registration started") - } - .subscribe({ - students.forEach { analytics.logEvent("registration_student_select", "success" to true, "scrapperBaseUrl" to it.scrapperBaseUrl, "symbol" to it.symbol, "error" to "No error") } - Timber.i("Registration result: Success") - view?.openMainView() - }, { error -> - students.forEach { analytics.logEvent("registration_student_select", "success" to false, "scrapperBaseUrl" to it.scrapperBaseUrl, "symbol" to it.symbol, "error" to error.message.ifNullOrBlank { "No message" }) } - Timber.i("Registration result: An exception occurred ") - loginErrorHandler.dispatch(error) - lastError = error - view?.apply { - showProgress(false) - showContent(true) - showContact(true) - } - })) + private fun onEnterSymbol() { + view?.navigateToSymbol(loginData) } - fun onDiscordClick() { + private fun onDiscordClick() { view?.openDiscordInvite() } - fun onEmailClick() { - view?.openEmail(lastError?.message.ifNullOrBlank { "empty" }) + private fun onEmailClick() { + view?.openEmail( + LoginSupportInfo( + loginData = loginData, + registerUser = registerUser, + lastErrorMessage = lastError?.message, + enteredSymbol = loginData.userEnteredSymbol, + ) + ) + } + + private fun logRegisterEvent( + studentsWithSemesters: List, + error: Throwable? = null + ) { + studentsWithSemesters.forEach { student -> + analytics.logEvent( + "registration_student_select", + "success" to (error != null), + "scrapperBaseUrl" to student.student.scrapperBaseUrl, + "symbol" to student.student.symbol, + "error" to (error?.message?.ifBlank { "No message" } ?: "No error") + ) + } + } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index d80b059d..4d0ef9e9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -1,14 +1,21 @@ package io.github.wulkanowy.ui.modules.login.studentselect +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginStudentSelectView : BaseView { + val symbols: Map + fun initView() fun updateData(data: List) - fun openMainView() + fun navigateToSymbol(loginData: LoginData) + + fun navigateToNext() fun showProgress(show: Boolean) @@ -16,9 +23,11 @@ interface LoginStudentSelectView : BaseView { fun enableSignIn(enable: Boolean) - fun showContact(show: Boolean) - fun openDiscordInvite() - fun openEmail(lastError: String) + fun openEmail(supportInfo: LoginSupportInfo) + + fun showAdminMessage(adminMessage: AdminMessage?) + + fun openInternetBrowser(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt new file mode 100644 index 00000000..4be2dbaa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt @@ -0,0 +1,149 @@ +package io.github.wulkanowy.ui.modules.login.support + +import android.os.Bundle +import android.util.Patterns +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.DialogLoginSupportBinding +import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.openEmailClient +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class LoginSupportDialog : BaseDialogFragment() { + + @Inject + lateinit var appInfo: AppInfo + + @Inject + lateinit var preferencesRepository: PreferencesRepository + + private lateinit var supportInfo: LoginSupportInfo + + companion object { + private const val ARGUMENT_KEY = "info" + + fun newInstance(info: LoginSupportInfo) = LoginSupportDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to info) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_FRAME, R.style.WulkanowyTheme_NoActionBar) + supportInfo = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = DialogLoginSupportBinding.inflate(inflater) + .apply { binding = this } + binding.dialogLoginSupportToolbar.setNavigationOnClickListener { dismiss() } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + dialogLoginSupportSchoolInput.doOnTextChanged { _, _, _, _ -> + with(dialogLoginSupportSchoolLayout) { + isErrorEnabled = false + error = null + } + } + dialogLoginSupportSubmit.setOnClickListener { onSubmitClick() } + } + } + + private fun onSubmitClick() { + when { + binding.dialogLoginSupportSchoolInput.text.isNullOrBlank() -> { + with(binding.dialogLoginSupportSchoolLayout) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + Patterns.EMAIL_ADDRESS.matcher( + binding.dialogLoginSupportSchoolInput.text.toString() + ).matches() -> { + with(binding.dialogLoginSupportSchoolLayout) { + isErrorEnabled = true + error = getString(R.string.login_support_school_invalid) + } + } + + else -> { + openEmailClientWithFilledTemplate() + dismiss() + } + } + } + + private fun openEmailClientWithFilledTemplate() { + with(binding) { + context?.openEmailClient( + chooserTitle = requireContext().getString(R.string.login_email_intent_title), + email = "wulkanowyinc@gmail.com", + subject = requireContext().getString(R.string.login_email_subject), + body = requireContext().getString( + R.string.login_email_text, + "${appInfo.systemManufacturer} ${appInfo.systemModel}", + appInfo.systemVersion.toString(), + "${appInfo.versionName}-${appInfo.buildFlavor}", + supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" }, + preferencesRepository.installationId, + getLastErrorFromStudentSelectScreen(), + dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() } + ?: return@with, + dialogLoginSupportAdditionalInput.text, + ) + ) + } + } + + private fun getLastErrorFromStudentSelectScreen(): String { + if (!supportInfo.lastErrorMessage.isNullOrBlank()) { + return supportInfo.lastErrorMessage!! + } + if (supportInfo.registerUser?.symbols.isNullOrEmpty()) { + return "" + } + + return "\n" + supportInfo.registerUser?.symbols?.filterNot { + (it.error is AccountPermissionException || it.error is InvalidSymbolException) && + it.symbol != supportInfo.enteredSymbol + }?.joinToString(";\n") { symbol -> + buildString { + append(" -") + append(symbol.symbol) + append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})") + if (symbol.schools.isNotEmpty()) { + append(": ") + } + append(symbol.schools.joinToString(", ") { unit -> + buildString { + append(unit.schoolShortName) + append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})") + } + }) + } + } + "\nPozostałe: " + supportInfo.registerUser?.symbols?.filter { + it.error is AccountPermissionException || it.error is InvalidSymbolException + }?.joinToString(", ") { it.symbol } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt new file mode 100644 index 00000000..3f101dd6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.login.support + +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.ui.modules.login.LoginData +import java.io.Serializable + +data class LoginSupportInfo( + val loginData: LoginData, + val registerUser: RegisterUser?, + val lastErrorMessage: String?, + val enteredSymbol: String?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 5602b624..a813e7c0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -1,28 +1,38 @@ package io.github.wulkanowy.ui.modules.login.symbol import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_NULL import android.widget.ArrayAdapter +import androidx.core.os.bundleOf +import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.hideSoftInput -import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.fragment_login_symbol.* import javax.inject.Inject -class LoginSymbolFragment : BaseFragment(), LoginSymbolView { +@AndroidEntryPoint +class LoginSymbolFragment : + BaseFragment(R.layout.fragment_login_symbol), LoginSymbolView { @Inject lateinit var presenter: LoginSymbolPresenter @@ -30,63 +40,99 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { private const val SAVED_LOGIN_DATA = "LOGIN_DATA" - fun newInstance() = LoginSymbolFragment() - } - - override val symbolNameError: CharSequence? - get() = loginSymbolNameLayout.error - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_login_symbol, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_LOGIN_DATA)) - } - - override fun initView() { - loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } - loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } - loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } - - loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } - - loginSymbolName.apply { - setOnEditorActionListener { _, id, _ -> - if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false - } - setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values))) + fun newInstance(loginData: LoginData) = LoginSymbolFragment().apply { + arguments = bundleOf(SAVED_LOGIN_DATA to loginData) } } - fun onParentInitSymbolFragment(loginData: Triple) { - presenter.onParentInitSymbolView(loginData) + override val symbolValue: String? get() = binding.loginSymbolName.text?.toString() + + override val symbolNameError: CharSequence? + get() = binding.loginSymbolNameLayout.error + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLoginSymbolBinding.bind(view) + presenter.onAttachView( + view = this, + loginData = requireArguments().serializable(SAVED_LOGIN_DATA), + ) + } + + override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + + with(binding) { + loginSymbolSignIn.setOnClickListener { presenter.attemptLogin() } + loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } + loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } + + loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } + + loginSymbolName.apply { + setOnEditorActionListener { _, id, _ -> + if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false + } + setAdapter( + ArrayAdapter( + context, + android.R.layout.simple_list_item_1, + resources.getStringArray(R.array.symbols_values) + ) + ) + } + } + } + + override fun setLoginToHeading(login: String) { + binding.loginSymbolHeader.text = + getString(R.string.login_header_symbol, login).parseAsHtml() } override fun setErrorSymbolIncorrect() { - loginSymbolNameLayout.apply { + binding.loginSymbolNameLayout.apply { requestFocus() error = getString(R.string.login_incorrect_symbol) } } - override fun setErrorSymbolRequire() { - loginSymbolNameLayout.apply { + override fun setErrorSymbolInvalid() { + with(binding.loginSymbolNameLayout) { requestFocus() - error = getString(R.string.login_field_required) + error = getString(R.string.login_invalid_symbol) + } + } + + override fun setErrorSymbolDefinitelyInvalid() { + with(binding.loginSymbolNameLayout) { + requestFocus() + error = getString(R.string.login_invalid_symbol_definitely) + } + } + + override fun setErrorSymbolRequire() { + setErrorSymbol(getString(R.string.error_field_required)) + } + + override fun setErrorSymbol(message: String) { + with(binding.loginSymbolNameLayout) { + requestFocus() + error = message } } override fun clearSymbolError() { - loginSymbolNameLayout.error = null + binding.loginSymbolNameLayout.error = null } override fun clearAndFocusSymbol() { - loginSymbolNameLayout.apply { + binding.loginSymbolNameLayout.apply { editText?.text = null requestFocus() } @@ -101,15 +147,15 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override fun showProgress(show: Boolean) { - loginSymbolProgress.visibility = if (show) VISIBLE else GONE + binding.loginSymbolProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - loginSymbolContainer.visibility = if (show) VISIBLE else GONE + binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE } - override fun notifyParentAccountLogged(students: List) { - (activity as? LoginActivity)?.onSymbolFragmentAccountLogged(students) + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun onSaveInstanceState(outState: Bundle) { @@ -118,30 +164,35 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView { } override fun showContact(show: Boolean) { - loginSymbolContact.visibility = if (show) VISIBLE else GONE + binding.loginSymbolContact.visibility = if (show) VISIBLE else GONE } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } override fun openFaqPage() { - context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/co-to-jest-symbol", ::showMessage) - } - - override fun openEmail(host: String, lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString(R.string.login_email_text, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", - appInfo.systemVersion.toString(), - appInfo.versionName, - "$host/${loginSymbolName.text}", - lastError - ) + context?.openInternetBrowser( + "https://wulkanowy.github.io/czesto-zadawane-pytania/co-to-jest-symbol", + ::showMessage ) } + + override fun openSupportDialog(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") + } + + override fun showAdminMessage(adminMessage: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginSymbolAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessage) + binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt index 1f9e66b7..8de2994a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt @@ -1,96 +1,179 @@ package io.github.wulkanowy.ui.modules.login.symbol -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase +import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.ifNullOrBlank -import io.reactivex.Single +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.io.Serializable import javax.inject.Inject class LoginSymbolPresenter @Inject constructor( studentRepository: StudentRepository, - schedulers: SchedulersProvider, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(loginErrorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper, + private val preferencesRepository: PreferencesRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, +) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null - var loginData: Triple? = null + lateinit var loginData: LoginData - @Suppress("UNCHECKED_CAST") - fun onAttachView(view: LoginSymbolView, savedLoginData: Serializable?) { + private var registerUser: RegisterUser? = null + + fun onAttachView(view: LoginSymbolView, loginData: LoginData) { super.onAttachView(view) - view.run { + this.loginData = loginData + loginErrorHandler.onBadCredentials = { + view.setErrorSymbol(it.orEmpty()) + } + with(view) { initView() showContact(false) + setLoginToHeading(loginData.login) + clearAndFocusSymbol() + showSoftKeyboard() } - if (savedLoginData is Triple<*, *, *>) { - loginData = savedLoginData as Triple + + loadAdminMessage() + } + + private fun loadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = loginData.baseUrl, + type = MessageType.LOGIN_SYMBOL_MESSAGE, + ) } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch("load_admin_message") } fun onSymbolTextChanged() { view?.apply { if (symbolNameError != null) clearSymbolError() } } - fun attemptLogin(symbol: String) { - if (loginData == null) throw IllegalArgumentException("Login data is null") - - if (symbol.isBlank()) { + fun attemptLogin() { + if (view?.symbolValue.isNullOrBlank()) { view?.setErrorSymbolRequire() return } + if (isFormDefinitelyInvalid()) { + view?.setErrorSymbolDefinitelyInvalid() + return + } - disposable.add( - Single.fromCallable { loginData } - .flatMap { studentRepository.getStudentsScrapper(it.first, it.second, it.third, symbol) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - hideSoftKeyboard() - showProgress(true) - showContent(false) - } + loginData = loginData.copy( + userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(), + ) + resourceFlow { + studentRepository.getUserSubjectsFromScrapper( + email = loginData.login, + password = loginData.password, + scrapperBaseUrl = loginData.baseUrl, + domainSuffix = loginData.domainSuffix, + symbol = loginData.userEnteredSymbol.orEmpty(), + ) + }.onEach { user -> + registerUser = user.dataOrNull + when (user) { + is Resource.Loading -> view?.run { Timber.i("Login with symbol started") + hideSoftKeyboard() + showProgress(true) + showContent(false) } - .doFinally { - view?.apply { - showProgress(false) - showContent(true) - } - } - .subscribe({ - analytics.logEvent("registration_symbol", "success" to true, "students" to it.size, "scrapperBaseUrl" to loginData?.third, "symbol" to symbol, "error" to "No error") - view?.apply { - if (it.isEmpty()) { + + is Resource.Success -> { + when (user.data.symbols.size) { + 0 -> { Timber.i("Login with symbol result: Empty student list") - setErrorSymbolIncorrect() - view?.showContact(true) - } else { - Timber.i("Login with symbol result: Success") - notifyParentAccountLogged(it) + view?.run { + setErrorSymbolIncorrect() + showContact(true) + } + } + + else -> { + val enteredSymbolDetails = user.data.symbols + .firstOrNull() + ?.takeIf { it.symbol == loginData.userEnteredSymbol } + + if (enteredSymbolDetails?.error is InvalidSymbolException) { + showInvalidSymbolError() + } else { + Timber.i("Login with symbol result: Success") + view?.navigateToStudentSelect(loginData, requireNotNull(user.data)) + } } } - }, { + analytics.logEvent( + "registration_symbol", + "success" to true, + "scrapperBaseUrl" to loginData.baseUrl, + "symbol" to view?.symbolValue, + "error" to "No error" + ) + } + + is Resource.Error -> { Timber.i("Login with symbol result: An exception occurred") - analytics.logEvent("registration_symbol", "success" to false, "students" to -1, "scrapperBaseUrl" to loginData?.third, "symbol" to symbol, "error" to it.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it) - lastError = it + analytics.logEvent( + "registration_symbol", + "success" to false, + "students" to -1, + "scrapperBaseUrl" to loginData.baseUrl, + "symbol" to view?.symbolValue, + "error" to user.error.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(user.error) + lastError = user.error view?.showContact(true) - })) + if (user.error is InvalidSymbolException) { + showInvalidSymbolError() + } + } + } + }.onResourceNotLoading { + view?.apply { + showProgress(false) + showContent(true) + } + }.launch("login") } - fun onParentInitSymbolView(loginData: Triple) { - this.loginData = loginData - view?.apply { - clearAndFocusSymbol() - showSoftKeyboard() + private fun isFormDefinitelyInvalid(): Boolean { + val definitelyInvalidSymbols = listOf("vulcan", "uonet", "wulkanowy", "standardowa") + val normalizedSymbol = view?.symbolValue.orEmpty().getNormalizedSymbol() + + return normalizedSymbol in definitelyInvalidSymbols + } + + private fun showInvalidSymbolError() { + view?.run { + setErrorSymbolInvalid() + showContact(true) } } @@ -99,6 +182,23 @@ class LoginSymbolPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(loginData?.third.orEmpty(), lastError?.message.ifNullOrBlank { "empty" }) + view?.openSupportDialog( + LoginSupportInfo( + loginData = loginData, + registerUser = registerUser, + lastErrorMessage = lastError?.message, + enteredSymbol = view?.symbolValue, + ) + ) + } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 6938bf13..2fc91024 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -1,18 +1,31 @@ package io.github.wulkanowy.ui.modules.login.symbol -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginSymbolView : BaseView { + val symbolValue: String? + val symbolNameError: CharSequence? fun initView() + fun setLoginToHeading(login: String) + fun setErrorSymbolIncorrect() + fun setErrorSymbolInvalid() + + fun setErrorSymbolDefinitelyInvalid() + fun setErrorSymbolRequire() + fun setErrorSymbol(message: String) + fun clearSymbolError() fun clearAndFocusSymbol() @@ -25,11 +38,15 @@ interface LoginSymbolView : BaseView { fun showContent(show: Boolean) - fun notifyParentAccountLogged(students: List) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun showContact(show: Boolean) fun openFaqPage() - fun openEmail(host: String, lastError: String) + fun openSupportDialog(supportInfo: LoginSupportInfo) + + fun showAdminMessage(adminMessage: AdminMessage?) + + fun openInternetBrowser(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt index 12bf1a13..a6c75c1d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt @@ -1,19 +1,24 @@ package io.github.wulkanowy.ui.modules.luckynumber import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.FragmentLuckyNumberBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import kotlinx.android.synthetic.main.fragment_lucky_number.* +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView { +@AndroidEntryPoint +class LuckyNumberFragment : + BaseFragment(R.layout.fragment_lucky_number), LuckyNumberView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: LuckyNumberPresenter @@ -25,58 +30,72 @@ class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView override val titleStringId: Int get() = R.string.lucky_number_title - override val isViewEmpty get() = luckyNumberText.text.isBlank() + override val isViewEmpty get() = binding.luckyNumberText.text.isBlank() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_lucky_number, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = luckyNumberSwipe + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLuckyNumberBinding.bind(view) + messageContainer = binding.luckyNumberSwipe presenter.onAttachView(this) } override fun initView() { - luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() } - luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + luckyNumberSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + luckyNumberSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + luckyNumberSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + luckyNumberHistoryButton.setOnClickListener { openLuckyNumberHistory() } + luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() } + luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } } override fun updateData(data: LuckyNumber) { - luckyNumberText.text = data.luckyNumber.toString() + binding.luckyNumberText.text = data.luckyNumber.toString() } override fun hideRefresh() { - luckyNumberSwipe.isRefreshing = false + binding.luckyNumberSwipe.isRefreshing = false } override fun showEmpty(show: Boolean) { - luckyNumberEmpty.visibility = if (show) VISIBLE else GONE + binding.luckyNumberEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - luckyNumberError.visibility = if (show) VISIBLE else GONE + binding.luckyNumberError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - luckyNumberErrorMessage.text = message + binding.luckyNumberErrorMessage.text = message } override fun showProgress(show: Boolean) { - luckyNumberProgress.visibility = if (show) VISIBLE else GONE + binding.luckyNumberProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - luckyNumberSwipe.isEnabled = enable + binding.luckyNumberSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - luckyNumberContent.visibility = if (show) VISIBLE else GONE + binding.luckyNumberContent.visibility = if (show) VISIBLE else GONE + } + + override fun openLuckyNumberHistory() { + (activity as? MainActivity)?.pushView(LuckyNumberHistoryFragment.newInstance()) + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun popView() { + (activity as? MainActivity)?.popView() } override fun onDestroyView() { - super.onDestroyView() presenter.onDetachView() + super.onDestroyView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 1d3a17bd..2039624b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.luckynumber -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject class LuckyNumberPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val luckyNumberRepository: LuckyNumberRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -32,42 +31,45 @@ class LuckyNumberPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading lucky number started") - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMapMaybe { luckyNumberRepository.getLuckyNumber(it, forceRefresh) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading lucky number result: Success") + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + luckyNumberRepository.getLuckyNumber(student, forceRefresh) + } + .logResourceStatus("load lucky number") + .onResourceData { + if (it != null) { view?.apply { updateData(it) showContent(true) showEmpty(false) showErrorView(false) } - analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh) - }, { - Timber.i("Loading lucky number result: An exception occurred") - errorHandler.dispatch(it) - }, { - Timber.i("Loading lucky number result: No lucky number found") + } else { view?.run { showContent(false) showEmpty(true) showErrorView(false) } - }) - ) - } + } + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "lucky_number", + "number" to it.luckyNumber + ) + } + } + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -97,4 +99,9 @@ class LuckyNumberPresenter @Inject constructor( fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } + + fun onViewReselected() { + Timber.i("Luckynumber view is reselected") + view?.popView() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt index a680c83e..3d34ebc8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt @@ -24,4 +24,8 @@ interface LuckyNumberView : BaseView { fun enableSwipe(enable: Boolean) fun showContent(show: Boolean) + + fun openLuckyNumberHistory() + + fun popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt new file mode 100644 index 00000000..9c718af4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.ItemLuckyNumberHistoryBinding +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import javax.inject.Inject + +class LuckyNumberHistoryAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemLuckyNumberHistoryBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("DefaultLocale") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + with(holder.binding) { + luckyNumberHistoryWeekName.text = item.date.weekDayName.capitalise() + luckyNumberHistoryDate.text = item.date.toFormattedString() + luckyNumberHistory.text = item.luckyNumber.toString() + } + } + + class ItemViewHolder(val binding: ItemLuckyNumberHistoryBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt new file mode 100644 index 00000000..a78ce5dd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -0,0 +1,129 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import android.os.Bundle +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.FragmentLuckyNumberHistoryBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class LuckyNumberHistoryFragment : + BaseFragment(R.layout.fragment_lucky_number_history), + LuckyNumberHistoryView, + MainView.TitledView { + + @Inject + lateinit var presenter: LuckyNumberHistoryPresenter + + @Inject + lateinit var luckyNumberHistoryAdapter: LuckyNumberHistoryAdapter + + companion object { + fun newInstance() = LuckyNumberHistoryFragment() + } + + override val titleStringId: Int + get() = R.string.lucky_number_history_title + + override val isViewEmpty get() = luckyNumberHistoryAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLuckyNumberHistoryBinding.bind(view) + messageContainer = binding.luckyNumberHistoryRecycler + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.luckyNumberHistoryRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = luckyNumberHistoryAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + luckyNumberHistoryNavDate.setOnClickListener { presenter.onPickDate() } + luckyNumberHistoryErrorRetry.setOnClickListener { presenter.onRetry() } + luckyNumberHistoryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + + luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() } + luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() } + + luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(3f) + } + } + + override fun updateData(data: List) { + with(luckyNumberHistoryAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(luckyNumberHistoryAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showEmpty(show: Boolean) { + binding.luckyNumberHistoryEmpty.visibility = if (show) VISIBLE else GONE + } + + override fun showErrorView(show: Boolean) { + binding.luckyNumberHistoryError.visibility = if (show) VISIBLE else GONE + } + + override fun setErrorDetails(message: String) { + binding.luckyNumberHistoryErrorMessage.text = message + } + + override fun updateNavigationWeek(date: String) { + binding.luckyNumberHistoryNavDate.text = date + } + + override fun showProgress(show: Boolean) { + binding.luckyNumberHistoryProgress.visibility = if (show) VISIBLE else GONE + } + + override fun showPreButton(show: Boolean) { + binding.luckyNumberHistoryPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + } + + override fun showNextButton(show: Boolean) { + binding.luckyNumberHistoryNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + } + + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().plusWeeks(1), + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) + } + + override fun showContent(show: Boolean) { + binding.luckyNumberHistoryRecycler.visibility = if (show) VISIBLE else GONE + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt new file mode 100644 index 00000000..fc753950 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt @@ -0,0 +1,155 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.* +import timber.log.Timber +import java.time.LocalDate +import javax.inject.Inject + +class LuckyNumberHistoryPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val luckyNumberRepository: LuckyNumberRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + var currentDate: LocalDate = LocalDate.now().previousOrSameSchoolDay + + override fun onAttachView(view: LuckyNumberHistoryView) { + super.onAttachView(view) + view.run { + initView() + reloadNavigation() + showContent(false) + } + Timber.i("Lucky number history view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + if (currentDate.isHolidays) setBaseDateOnHolidays() + } + + private fun setBaseDateOnHolidays() { + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) + reloadNavigation() + } + .launch("holidays") + } + + private fun loadData() { + flow { + val student = studentRepository.getCurrentStudent() + emitAll( + luckyNumberRepository.getLuckyNumberHistory( + student = student, + start = currentDate.monday, + end = currentDate.sunday + ) + ) + } + .onEach { + if (!it.isNullOrEmpty()) { + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + showProgress(false) + } + } else { + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) + showProgress(false) + } + } + + analytics.logEvent( + "load_items", + "type" to "lucky_number_history", + ) + } + .catch { errorHandler.dispatch(it) } + .launchIn(presenterScope) + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun reloadView(date: LocalDate) { + currentDate = date + Timber.i("Reload lucky number history view with the date ${currentDate.toFormattedString()}") + view?.apply { + showProgress(true) + showContent(false) + showEmpty(false) + showErrorView(false) + clearData() + reloadNavigation() + } + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun reloadNavigation() { + view?.apply { + showPreButton(!currentDate.minusDays(7).isHolidays) + showNextButton(!currentDate.plusDays(7).isHolidays) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) + } + } + + fun onDateSet(year: Int, month: Int, day: Int) { + reloadView(LocalDate.of(year, month, day)) + loadData() + } + + fun onPickDate() { + view?.showDatePickerDialog(currentDate) + } + + fun onPreviousWeek() { + reloadView(currentDate.minusDays(7)) + loadData() + } + + fun onNextWeek() { + reloadView(currentDate.plusDays(7)) + loadData() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt new file mode 100644 index 00000000..7b9b0294 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate + +interface LuckyNumberHistoryView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun updateNavigationWeek(date: String) + + fun showProgress(show: Boolean) + + fun showPreButton(show: Boolean) + + fun showNextButton(show: Boolean) + + fun showDatePickerDialog(selectedDate: LocalDate) + + fun showContent(show: Boolean) + + fun onDestroyView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index e8ce3bcf..a2d23e54 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -1,69 +1,62 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.R +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.activity_widget_configure.* +import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject -class LuckyNumberWidgetConfigureActivity : BaseActivity(), +@AndroidEntryPoint +class LuckyNumberWidgetConfigureActivity : + BaseActivity(), LuckyNumberWidgetConfigureView { @Inject - lateinit var configureAdapter: FlexibleAdapter> + lateinit var configureAdapter: WidgetConfigureAdapter @Inject override lateinit var presenter: LuckyNumberWidgetConfigurePresenter + @Inject + lateinit var appInfo: AppInfo + private var dialog: AlertDialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) - setContentView(R.layout.activity_widget_configure) - + setContentView( + ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root + ) intent.extras.let { presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) } } override fun initView() { - with(widgetConfigureRecycler) { + with(binding.widgetConfigureRecycler) { adapter = configureAdapter - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) } - configureAdapter.setOnItemClickListener(presenter::onItemSelect) + configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - val items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - - override fun updateData(data: List) { - configureAdapter.updateDataSet(data) + override fun updateData(data: List, selectedStudentId: Long) { + with(configureAdapter) { + selectedId = selectedStudentId + items = data + notifyDataSetChanged() + } } override fun updateLuckyNumberWidget(widgetId: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt deleted file mode 100644 index e260b7fc..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.ui.modules.luckynumberwidget - -import android.annotation.SuppressLint -import android.view.View -import androidx.core.graphics.ColorUtils -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_account.* - -class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolean) : - AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - val context = holder.contentView.context - - val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableWidgetConfigureItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 6e4716bf..7e53dad0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -1,22 +1,22 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.SchedulersProvider +import kotlinx.coroutines.flow.onEach +import timber.log.Timber import javax.inject.Inject class LuckyNumberWidgetConfigurePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val sharedPref: SharedPrefProvider -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { private var appWidgetId: Int? = null @@ -29,42 +29,28 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( loadData() } - fun onItemSelect(item: AbstractFlexibleItem<*>) { - if (item is LuckyNumberWidgetConfigureItem) { - selectedStudent = item.student - view?.showThemeDialog() - } - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } + fun onItemSelect(student: Student) { + selectedStudent = student registerStudent(selectedStudent) } - fun onDismissThemeView(){ - view?.finishView() - } - private fun loadData() { - disposable.add(studentRepository.getSavedStudents(false) - .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } - .map { (students, currentStudentId) -> - students.map { student -> LuckyNumberWidgetConfigureItem(student, student.id == currentStudentId) } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - when { - it.isEmpty() -> view?.openLoginView() - it.size == 1 -> { - selectedStudent = it.single().student - view?.showThemeDialog() + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Lucky number widget configure students data load") + is Resource.Success -> { + val selectedStudentId = appWidgetId?.let { id -> + sharedPref.getLong(getStudentWidgetKey(id), 0) + } ?: -1 + when { + it.data.isEmpty() -> view?.openLoginView() + it.data.size == 1 -> onItemSelect(it.data.single().student) + else -> view?.updateData(it.data, selectedStudentId) } - else -> view?.updateData(it) } - }, { errorHandler.dispatch(it) })) + is Resource.Error -> errorHandler.dispatch(it.error) + } + }.launch() } private fun registerStudent(student: Student?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt index fa4c0cc6..df13b993 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -1,14 +1,13 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView interface LuckyNumberWidgetConfigureView : BaseView { fun initView() - fun showThemeDialog() - - fun updateData(data: List) + fun updateData(data: List, selectedStudentId: Long) fun updateLuckyNumberWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index e4b1072b..e6de1781 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -1,31 +1,29 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH import android.appwidget.AppWidgetProvider import android.content.Context -import android.content.Intent +import android.content.res.Configuration import android.os.Bundle -import android.view.View.GONE -import android.view.View.VISIBLE +import android.util.TypedValue.COMPLEX_UNIT_SP +import android.view.View import android.widget.RemoteViews -import dagger.android.AndroidInjection +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.dataOrThrow import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Maybe +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.toFirstResult +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.PendingIntentCompat +import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject +@AndroidEntryPoint class LuckyNumberWidgetProvider : AppWidgetProvider() { @Inject @@ -34,132 +32,146 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { @Inject lateinit var luckyNumberRepository: LuckyNumberRepository - @Inject - lateinit var schedulers: SchedulersProvider - - @Inject - lateinit var appWidgetManager: AppWidgetManager - @Inject lateinit var sharedPref: SharedPrefProvider companion object { + private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196 + + private const val LUCKY_NUMBER_PENDING_INTENT_ID = 300 + private const val LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID = 301 fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId" - - fun getHeightWidgetKey(appWidgetId: Int) = "lucky_number_widget_height_$appWidgetId" - - fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId" } - override fun onReceive(context: Context, intent: Intent) { - AndroidInjection.inject(this, context) - super.onReceive(context, intent) - } - - override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray?) { + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray? + ) { super.onUpdate(context, appWidgetManager, appWidgetIds) - appWidgetIds?.forEach { appWidgetId -> - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark - val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id, - MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT) + val appIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumber), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - val remoteView = RemoteViews(context.packageName, layoutId).apply { - setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#") - setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) - } + val historyIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumberHistory), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - setStyles(remoteView, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) + appWidgetIds?.forEach { widgetId -> + val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) + val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString() + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + .apply { + setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-") + setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) + setOnClickPendingIntent(R.id.luckyNumberWidgetHistoryButton, historyIntent) + } + + resizeWidget(context, appWidgetManager.getAppWidgetOptions(widgetId), remoteView) + appWidgetManager.updateAppWidget(widgetId, remoteView) + } + } + + override fun onAppWidgetOptionsChanged( + context: Context?, + appWidgetManager: AppWidgetManager?, + appWidgetId: Int, + newOptions: Bundle? + ) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + + if (context == null || newOptions == null || appWidgetManager == null) { + return + } + + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + resizeWidget(context, newOptions, remoteView) + appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteView) + } + + private fun resizeWidget(context: Context, options: Bundle, remoteViews: RemoteViews) { + val (width, height) = options.getWidgetSize(context) + val size = minOf(width, height, LUCKY_NUMBER_WIDGET_MAX_SIZE).toFloat() + resizeWidgetContents(size, remoteViews) + Timber.v("LuckyNumberWidget resized: ${width}x${height} ($size)") + } + + private fun resizeWidgetContents(size: Float, remoteViews: RemoteViews) { + var historyButtonVisibility = View.VISIBLE + var luckyNumberTextSize = 72f + + if (size < 150) { + luckyNumberTextSize = 44f + historyButtonVisibility = View.GONE + } + if (size < 75) { + luckyNumberTextSize = 26f + } + + remoteViews.apply { + setTextViewTextSize(R.id.luckyNumberWidgetValue, COMPLEX_UNIT_SP, luckyNumberTextSize) + setViewVisibility(R.id.luckyNumberWidgetHistoryButton, historyButtonVisibility) } } override fun onDeleted(context: Context?, appWidgetIds: IntArray?) { super.onDeleted(context, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> - if (appWidgetId != 0) sharedPref.delete(getStudentWidgetKey(appWidgetId)) + sharedPref.delete(getStudentWidgetKey(appWidgetId)) } } - override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) { - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) - - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark - - val remoteView = RemoteViews(context.packageName, layoutId) - - setStyles(remoteView, appWidgetId, newOptions) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) - } - - private fun setStyles(views: RemoteViews, appWidgetId: Int, options: Bundle? = null) { - val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong(getWidthWidgetKey(appWidgetId), 74).toInt() - val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong(getHeightWidgetKey(appWidgetId), 74).toInt() - - sharedPref.putLong(getWidthWidgetKey(appWidgetId), width.toLong()) - sharedPref.putLong(getHeightWidgetKey(appWidgetId), height.toLong()) - - val rows = getCellsForSize(height) - val cols = getCellsForSize(width) - - Timber.d("New lucky number widget measurement: %dx%d", width, height) - Timber.d("Widget size: $cols x $rows") - - when { - 1 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = false) - 1 == cols && 1 < rows -> views.setVisibility(imageTop = true, imageLeft = false) - 1 < cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - 1 == cols && 1 == rows -> views.setVisibility(imageTop = true, imageLeft = false) - 2 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - else -> views.setVisibility(imageTop = false, imageLeft = false, title = true) - } - } - - private fun RemoteViews.setVisibility(imageTop: Boolean, imageLeft: Boolean, title: Boolean = false) { - setViewVisibility(R.id.luckyNumberWidgetImageTop, if (imageTop) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetImageLeft, if (imageLeft) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetTitle, if (title) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) - } - - private fun getCellsForSize(size: Int): Int { - var n = 2 - while (74 * n - 30 < size) ++n - return n - 1 - } - - private fun getLuckyNumber(studentId: Long, appWidgetId: Int): LuckyNumber? { - return try { - studentRepository.isStudentSaved() - .filter { true } - .flatMap { studentRepository.getSavedStudents().toMaybe() } - .flatMap { students -> - val student = students.singleOrNull { student -> student.id == studentId } - when { - student != null -> Maybe.just(student) - studentId != 0L -> { - studentRepository.isCurrentStudentSet() - .filter { true } - .flatMap { studentRepository.getCurrentStudent(false).toMaybe() } - .doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) } - } - else -> Maybe.empty() + private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking { + try { + val students = studentRepository.getSavedStudents() + val student = students.singleOrNull { it.student.id == studentId }?.student + val currentStudent = when { + student != null -> student + studentId != 0L && studentRepository.isCurrentStudentSet() -> { + studentRepository.getCurrentStudent(false).also { + sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) } } - .flatMap { luckyNumberRepository.getLuckyNumber(it) } - .subscribeOn(schedulers.backgroundThread) - .blockingGet() - } catch (e: Exception) { - if (e.cause !is NoCurrentStudentException) { - Timber.e(e, "An error has occurred in lucky number provider") + + else -> null } + + if (currentStudent != null) { + luckyNumberRepository.getLuckyNumber( + student = currentStudent, + forceRefresh = false, + isFromAppWidget = true + ) + .toFirstResult() + .dataOrThrow + } else null + } catch (e: Exception) { + Timber.e(e, "An error has occurred in lucky number provider") null } } + + private fun Bundle.getWidgetSize(context: Context): Pair { + val minWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) + val maxWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) + val minHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) + val maxHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) + + val orientation = context.resources.configuration.orientation + val isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT + + return if (isPortrait) { + minWidth to maxHeight + } else { + maxWidth to minHeight + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 864ad423..e64aa9b0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -2,63 +2,98 @@ package io.github.wulkanowy.ui.modules.main import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem -import androidx.core.graphics.ColorUtils +import android.view.ViewGroup.MarginLayoutParams +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment -import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW -import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.elevation.ElevationOverlayProvider import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController.Companion.HIDE -import dagger.Lazy +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.ActivityMainBinding import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.modules.account.AccountDialog -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.more.MoreFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog +import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.InAppReviewHelper +import io.github.wulkanowy.utils.InAppUpdateHelper +import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.safelyPopFragments import io.github.wulkanowy.utils.setOnViewChangeListener -import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds -class MainActivity : BaseActivity(), MainView { +@AndroidEntryPoint +class MainActivity : BaseActivity(), MainView, + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { @Inject override lateinit var presenter: MainPresenter @Inject - lateinit var navController: FragNavController + lateinit var analytics: AnalyticsHelper @Inject - lateinit var overlayProvider: Lazy + lateinit var inAppUpdateHelper: InAppUpdateHelper + + @Inject + lateinit var inAppReviewHelper: InAppReviewHelper + + @Inject + lateinit var appInfo: AppInfo + + private var onBackCallback: OnBackPressedCallback? = null + + private var accountMenu: MenuItem? = null + + private val overlayProvider by lazy { ElevationOverlayProvider(this) } + + private val navController = + FragNavController(supportFragmentManager, R.id.main_fragment_container) + + private val captchaVerificationEvent = MutableSharedFlow() companion object { - const val EXTRA_START_MENU = "extraStartMenu" - fun getStartIntent(context: Context, startMenu: MainView.Section? = null, clear: Boolean = false): Intent { - return Intent(context, MainActivity::class.java) - .apply { - if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK - startMenu?.let { putExtra(EXTRA_START_MENU, it) } - } + private const val EXTRA_START_DESTINATION = "start_destination_json" + + fun getStartIntent( + context: Context, + destination: Destination? = null, + ) = Intent(context, MainActivity::class.java).apply { + destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) } } } @@ -66,79 +101,142 @@ class MainActivity : BaseActivity(), MainView { override val currentStackSize get() = navController.currentStack?.size - override val currentViewTitle get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) } + override val currentViewTitle + get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId + ?.let { getString(it) } override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString - override var startMenuIndex = 0 - - override var startMenuMoreIndex = -1 - - private val moreMenuFragments = mapOf( - MainView.Section.MESSAGE.id to MessageFragment.newInstance(), - MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(), - MainView.Section.NOTE.id to NoteFragment.newInstance(), - MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance() - ) + private var savedInstanceState: Bundle? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - setSupportActionBar(mainToolbar) - messageContainer = mainFragmentContainer + setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) + setSupportActionBar(binding.mainToolbar) - presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section) - - with(navController) { - initialize(startMenuIndex, savedInstanceState) - pushFragment(moreMenuFragments[startMenuMoreIndex]) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.mainAppBar.isLifted = true } + initializeFragmentContainer() + + this.savedInstanceState = savedInstanceState + messageContainer = binding.mainMessageContainer + messageAnchor = binding.mainMessageContainer + inAppUpdateHelper.messageContainer = binding.mainFragmentContainer + onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) { + presenter.onBackPressed() + } + + val destination = intent.getStringExtra(EXTRA_START_DESTINATION) + ?.takeIf { savedInstanceState == null } + + presenter.onAttachView(this, destination) + inAppUpdateHelper.checkAndInstallUpdates() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + override fun onResume() { + super.onResume() + inAppUpdateHelper.onResume() + presenter.updateSdkMappings() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.action_menu_main, menu) + accountMenu = menu.findItem(R.id.mainMenuAccount) + + presenter.onActionMenuCreated() return true } - override fun initView() { - with(mainToolbar) { - if (SDK_INT >= LOLLIPOP) stateListAnimator = null - setBackgroundColor(overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))) - } - - with(mainBottomNav) { - addItems(listOf( - AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0), - AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_main_attendance, 0), - AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_main_exam, 0), - AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_main_timetable, 0), - AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0) - )) - accentColor = getThemeAttrColor(R.attr.colorPrimary) - inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153) - defaultBackgroundColor = overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f)) - titleState = ALWAYS_SHOW - currentItem = startMenuIndex - isBehaviorTranslationEnabled = false - setTitleTextSizeInSp(10f, 10f) - setOnTabSelectedListener(presenter::onTabSelected) - } + override fun initView( + startMenuIndex: Int, + rootAppMenuItems: List, + rootUpdatedDestinations: List + ) { + initializeToolbar() + initializeBottomNavigation(startMenuIndex, rootAppMenuItems) + initializeNavController(startMenuIndex, rootUpdatedDestinations) + initializeCaptchaVerificationEvent() + } + private fun initializeNavController( + startMenuIndex: Int, + rootUpdatedDestinations: List + ) { with(navController) { - setOnViewChangeListener(presenter::onViewChange) + setOnViewChangeListener { destinationView -> + presenter.onViewChange(destinationView) + analytics.setCurrentScreen( + this@MainActivity, + destinationView::class.java.simpleName + ) + } fragmentHideStrategy = HIDE - rootFragments = listOf( - GradeFragment.newInstance(), - AttendanceFragment.newInstance(), - ExamFragment.newInstance(), - TimetableFragment.newInstance(), - MoreFragment.newInstance() + rootFragments = rootUpdatedDestinations.map { it.destinationFragment } + + initialize(startMenuIndex, savedInstanceState) + } + savedInstanceState = null + } + + private fun initializeToolbar() { + with(binding.mainToolbar) { + stateListAnimator = null + setBackgroundColor( + overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f)) ) } } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected() + private fun initializeBottomNavigation( + startMenuIndex: Int, + rootAppMenuItems: List + ) { + with(binding.mainBottomNav) { + with(menu) { + rootAppMenuItems.forEachIndexed { index, item -> + add(Menu.NONE, index, Menu.NONE, item.title) + .setIcon(item.icon) + } + add(Menu.NONE, 4, Menu.NONE, R.string.more_title) + .setIcon(R.drawable.ic_main_more) + } + selectedItemId = startMenuIndex + setOnItemSelectedListener { + this@MainActivity.presenter.onTabSelected(it.itemId, false) + } + setOnItemReselectedListener { + this@MainActivity.presenter.onTabSelected(it.itemId, true) + } + } + } + + private fun initializeFragmentContainer() { + ViewCompat.setOnApplyWindowInsetsListener(binding.mainFragmentContainer) { view, insets -> + val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + + view.updateLayoutParams { + bottomMargin = if (binding.mainBottomNav.isVisible) 0 else bottomInsets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + + override fun onPreferenceStartFragment( + caller: PreferenceFragmentCompat, + pref: Preference + ): Boolean { + val fragment = supportFragmentManager.fragmentFactory.instantiate( + classLoader, + pref.fragment.toString() + ) + pushView(fragment) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected() else false } @@ -147,6 +245,9 @@ class MainActivity : BaseActivity(), MainView { } override fun switchMenuView(position: Int) { + if (supportFragmentManager.isStateSaved) return + + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.switchTab(position) } @@ -162,12 +263,17 @@ class MainActivity : BaseActivity(), MainView { supportActionBar?.setDisplayHomeAsUpEnabled(show) } - override fun showAccountPicker() { - navController.showDialogFragment(AccountDialog.newInstance()) + override fun showAccountPicker(studentWithSemesters: List) { + showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters)) } - override fun showActionBarElevation(show: Boolean) { - ViewCompat.setElevation(mainToolbar, if (show) dpToPx(4f) else 0f) + override fun showBottomNavigation(show: Boolean) { + binding.mainBottomNav.isVisible = show + binding.mainFragmentContainer.requestApplyInsets() + } + + override fun openMoreDestination(destination: Destination) { + pushView(destination.destinationFragment) } override fun notifyMenuViewReselected() { @@ -179,25 +285,81 @@ class MainActivity : BaseActivity(), MainView { (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentChanged() } + @Suppress("DEPRECATION") fun showDialogFragment(dialog: DialogFragment) { + if (supportFragmentManager.isStateSaved) return + + //Deprecated method is used here to avoid fragnav bug + if (navController.currentDialogFrag?.fragmentManager == null) { + FragNavController::class.java.getDeclaredField("mCurrentDialogFrag").apply { + isAccessible = true + set(navController, null) + } + } + navController.showDialogFragment(dialog) } fun pushView(fragment: Fragment) { + if (supportFragmentManager.isStateSaved) return + + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.pushFragment(fragment) + onBackCallback?.isEnabled = !isRootView } override fun popView(depth: Int) { + if (supportFragmentManager.isStateSaved) return + + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.safelyPopFragments(depth) + onBackCallback?.isEnabled = !isRootView } - override fun onBackPressed() { - presenter.onBackPressed { super.onBackPressed() } + override fun showStudentAvatar(student: Student) { + accountMenu?.run { + icon = createNameInitialsDrawable(student.nickOrName, student.avatarColor, 0.44f) + title = getString(R.string.main_account_picker) + } + } + + override fun showInAppReview() { + inAppReviewHelper.showInAppReview(this) + } + + override fun showAppSupport() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.main_support_title) + .setMessage(R.string.main_support_description) + .setPositiveButton(R.string.main_support_positive) { _, _ -> presenter.onEnableAdsSelected() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .setOnDismissListener { } + .show() + } + + @OptIn(FlowPreview::class) + private fun initializeCaptchaVerificationEvent() { + captchaVerificationEvent + .debounce(1.seconds) + .onEach { url -> + Timber.d("Showing captcha dialog for: $url") + showDialogFragment(CaptchaDialog.newInstance(url)) + } + .launchIn(lifecycleScope) + } + + override fun onCaptchaVerificationRequired(url: String?) { + lifecycleScope.launch { + captchaVerificationEvent.emit(url) + } + } + + override fun showAuthDialog() { + showDialogFragment(AuthDialog.newInstance()) } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) - intent.removeExtra(EXTRA_START_MENU) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt deleted file mode 100644 index b533684d..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt +++ /dev/null @@ -1,146 +0,0 @@ -package io.github.wulkanowy.ui.modules.main - -import com.google.android.material.elevation.ElevationOverlayProvider -import com.ncapdevi.fragnav.FragNavController -import dagger.Module -import dagger.Provides -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.R -import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.about.AboutFragment -import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment -import io.github.wulkanowy.ui.modules.about.license.LicenseFragment -import io.github.wulkanowy.ui.modules.about.license.LicenseModule -import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment -import io.github.wulkanowy.ui.modules.account.AccountDialog -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.attendance.AttendanceModule -import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.grade.GradeModule -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.message.MessageModule -import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment -import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment -import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule -import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog -import io.github.wulkanowy.ui.modules.more.MoreFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersModule -import io.github.wulkanowy.ui.modules.settings.SettingsFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment - -@Suppress("unused") -@Module -abstract class MainModule { - - companion object { - - @Provides - fun provideFragNavController(activity: MainActivity) = - FragNavController(activity.supportFragmentManager, R.id.mainFragmentContainer) - - //In activities must be injected as Lazy - @Provides - fun provideElevationOverlayProvider(activity: MainActivity) = ElevationOverlayProvider(activity) - } - - @PerFragment - @ContributesAndroidInjector(modules = [AttendanceModule::class]) - abstract fun bindAttendanceFragment(): AttendanceFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindAttendanceSummaryFragment(): AttendanceSummaryFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindExamFragment(): ExamFragment - - @PerFragment - @ContributesAndroidInjector(modules = [GradeModule::class]) - abstract fun bindGradeFragment(): GradeFragment - - @PerFragment - @ContributesAndroidInjector(modules = [MessageModule::class]) - abstract fun bindMessagesFragment(): MessageFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindMessagePreviewFragment(): MessagePreviewFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindMoreFragment(): MoreFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindTimetableFragment(): TimetableFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindAboutFragment(): AboutFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindSettingsFragment(): SettingsFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindNoteFragment(): NoteFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindHomeworkFragment(): HomeworkFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindHomeworkDetailsDialog(): HomeworkDetailsDialog - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLuckyNumberFragment(): LuckyNumberFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindCompletedLessonsFragment(): CompletedLessonsFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindAccountDialog(): AccountDialog - - @PerFragment - @ContributesAndroidInjector - abstract fun bindErrorDialog(): ErrorDialog - - @PerFragment - @ContributesAndroidInjector(modules = [MobileDeviceModule::class]) - abstract fun bindMobileDevices(): MobileDeviceFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindMobileDeviceDialog(): MobileDeviceTokenDialog - - @PerFragment - @ContributesAndroidInjector(modules = [LicenseModule::class]) - abstract fun bindLicenseFragment(): LicenseFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindLogViewerFragment(): LogViewerFragment - - @PerFragment - @ContributesAndroidInjector - abstract fun bindContributorFragment(): ContributorFragment - - @PerFragment - @ContributesAndroidInjector(modules = [SchoolAndTeachersModule::class]) - abstract fun bindSchoolAndTeachersFragment(): SchoolAndTeachersFragment -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index f5a49004..6a072718 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -1,45 +1,105 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.WulkanowyRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE -import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE -import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.account.AccountView +import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView +import io.github.wulkanowy.utils.AdsHelper +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json import timber.log.Timber +import java.time.Duration +import java.time.Instant import javax.inject.Inject class MainPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val prefRepository: PreferencesRepository, + private val preferencesRepository: PreferencesRepository, + private val wulkanowyRepository: WulkanowyRepository, private val syncManager: SyncManager, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper, + private val json: Json, + private val adsHelper: AdsHelper, + private val appInfo: AppInfo +) : BasePresenter(errorHandler, studentRepository) { - fun onAttachView(view: MainView, initMenu: MainView.Section?) { - super.onAttachView(view) - view.apply { - getProperViewIndexes(initMenu).let { (main, more) -> - startMenuIndex = main - startMenuMoreIndex = more + private var studentsWitSemesters: List? = null + + private val rootAppMenuItems = preferencesRepository.appMenuItemOrder + .sortedBy { it.order } + .take(4) + + private val rootDestinationTypeList = rootAppMenuItems.map { it.destinationType } + .plus(Destination.Type.MORE) + + private val Destination?.startMenuIndex + get() = when { + this == null -> 0 + destinationType in rootDestinationTypeList -> { + rootDestinationTypeList.indexOf(destinationType) } - initView() - Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index") + + else -> 4 + } + + fun onAttachView(view: MainView, initDestinationJson: String?) { + super.onAttachView(view) + + val initDestination: Destination? = initDestinationJson?.let { json.decodeFromString(it) } + + val startMenuIndex = initDestination.startMenuIndex + val destinations = rootDestinationTypeList.map { + if (it == initDestination?.destinationType) initDestination else it.defaultDestination + } + + view.initView(startMenuIndex, rootAppMenuItems, destinations) + if (initDestination != null && startMenuIndex == 4) { + view.openMoreDestination(initDestination) } syncManager.startPeriodicSyncWorker() - analytics.logEvent("app_open", "destination" to initMenu?.name) + + checkAppSupport() + updateCurrentStudentAuthStatus() + + analytics.logEvent("app_open", "destination" to initDestination.toString()) + Timber.i("Main view was initialized with $initDestination") } - fun onViewChange(section: MainView.Section?) { + fun onActionMenuCreated() { + if (!studentsWitSemesters.isNullOrEmpty()) { + showCurrentStudentAvatar() + return + } + + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load student avatar") + .onResourceSuccess { + studentsWitSemesters = it + showCurrentStudentAvatar() + } + .onResourceError(errorHandler::dispatch) + .launch("avatar") + } + + fun onViewChange(destinationView: BaseView) { view?.apply { - showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL) + showBottomNavigation(shouldShowBottomNavigation(destinationView)) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } currentStackSize?.let { @@ -49,9 +109,19 @@ class MainPresenter @Inject constructor( } } + private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) { + is AccountView, + is StudentInfoView, + is AccountDetailsView -> false + + else -> true + } + fun onAccountManagerSelected(): Boolean { + if (studentsWitSemesters.isNullOrEmpty()) return true + Timber.i("Select account manager") - view?.showAccountPicker() + view?.showAccountPicker(studentsWitSemesters!!) return true } @@ -61,12 +131,9 @@ class MainPresenter @Inject constructor( return true } - fun onBackPressed(default: () -> Unit) { + fun onBackPressed() { Timber.i("Back pressed in main view") - view?.run { - if (isRootView) default() - else popView() - } + view?.popView() } fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { @@ -78,16 +145,67 @@ class MainPresenter @Inject constructor( } else { notifyMenuViewChanged() switchMenuView(index) + checkInAppReview() true } } == true } - private fun getProperViewIndexes(initMenu: MainView.Section?): Pair { - return when (initMenu?.id) { - in 0..3 -> initMenu!!.id to -1 - in 4..10 -> 4 to initMenu!!.id - else -> prefRepository.startMenuIndex to -1 + fun onEnableAdsSelected() { + preferencesRepository.isAdsEnabled = true + adsHelper.initialize() + } + + private fun checkInAppReview() { + preferencesRepository.inAppReviewCount++ + + if (preferencesRepository.inAppReviewDate == null) { + preferencesRepository.inAppReviewDate = Instant.now() + } + + if (!preferencesRepository.isAppReviewDone && preferencesRepository.inAppReviewCount >= 50 && + Instant.now().minus(Duration.ofDays(14)).isAfter(preferencesRepository.inAppReviewDate) + ) { + view?.showInAppReview() + preferencesRepository.isAppReviewDone = true + } + } + + private fun checkAppSupport() { + if (!preferencesRepository.isAppSupportShown && !preferencesRepository.isAdsEnabled + && appInfo.buildFlavor == "play" + ) { + presenterScope.launch { + val student = runCatching { studentRepository.getCurrentStudent(false) } + .onFailure { Timber.e(it) } + .getOrElse { return@launch } + + if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) { + preferencesRepository.isAppSupportShown = true + view?.showAppSupport() + } + } + } + } + + private fun showCurrentStudentAvatar() { + val currentStudent = + studentsWitSemesters?.singleOrNull { it.student.isCurrent }?.student ?: return + + view?.showStudentAvatar(currentStudent) + } + + private fun updateCurrentStudentAuthStatus() { + presenterScope.launch { + runCatching { studentRepository.updateCurrentStudentAuthStatus() } + .onFailure { errorHandler.dispatch(it) } + } + } + + fun updateSdkMappings() { + presenterScope.launch { + runCatching { wulkanowyRepository.fetchMapping() } + .onFailure { Timber.e(it) } } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 97b556e3..70a94fc8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -1,13 +1,13 @@ package io.github.wulkanowy.ui.modules.main +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem interface MainView : BaseView { - var startMenuIndex: Int - - var startMenuMoreIndex: Int - val isRootView: Boolean val currentViewTitle: String? @@ -16,15 +16,19 @@ interface MainView : BaseView { val currentStackSize: Int? - fun initView() + fun initView( + startMenuIndex: Int, + rootAppMenuItems: List, + rootUpdatedDestinations: List + ) fun switchMenuView(position: Int) fun showHomeArrow(show: Boolean) - fun showAccountPicker() + fun showAccountPicker(studentWithSemesters: List) - fun showActionBarElevation(show: Boolean) + fun showBottomNavigation(show: Boolean) fun notifyMenuViewReselected() @@ -36,6 +40,14 @@ interface MainView : BaseView { fun popView(depth: Int = 1) + fun showStudentAvatar(student: Student) + + fun showInAppReview() + + fun showAppSupport() + + fun openMoreDestination(destination: Destination) + interface MainChildView { fun onFragmentReselected() @@ -51,19 +63,4 @@ interface MainView : BaseView { get() = "" set(_) {} } - - enum class Section(val id: Int) { - GRADE(0), - ATTENDANCE(1), - EXAM(2), - TIMETABLE(3), - MORE(4), - MESSAGE(5), - HOMEWORK(6), - NOTE(7), - LUCKY_NUMBER(8), - SETTINGS(9), - ABOUT(10), - SCHOOL(11) - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index 7a3e135d..02bc13a1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -1,33 +1,44 @@ package io.github.wulkanowy.ui.modules.message import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED -import io.github.wulkanowy.data.repositories.message.MessageFolder.SENT -import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.enums.MessageFolder.SENT +import io.github.wulkanowy.data.enums.MessageFolder.TRASHED +import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_message.* import javax.inject.Inject -class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { +@AndroidEntryPoint +class MessageFragment : BaseFragment(R.layout.fragment_message), + MessageView, MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MessagePresenter - @Inject - lateinit var pagerAdapter: BaseFragmentPagerAdapter + private val pagerAdapter by lazy { + BaseFragmentPagerAdapter( + fragmentManager = childFragmentManager, + pagesCount = 3, + lifecycle = lifecycle, + ) + } companion object { fun newInstance() = MessageFragment() @@ -35,70 +46,124 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { override val titleStringId get() = R.string.message_title - override val currentPageIndex get() = messageViewPager.currentItem + override val currentPageIndex get() = binding.messageViewPager.currentItem - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessageBinding.bind(view) + messageContainer = binding.messageViewPager presenter.onAttachView(this) } override fun initView() { - with(pagerAdapter) { - containerId = messageViewPager.id - addFragmentsWithTitle(mapOf( - MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox), - MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent), - MessageTabFragment.newInstance(TRASHED) to getString(R.string.message_trash) - )) - } - - with(messageViewPager) { + with(binding.messageViewPager) { adapter = pagerAdapter offscreenPageLimit = 2 setOnSelectPageListener(presenter::onPageSelected) } - with(messageTabLayout) { - setupWithViewPager(messageViewPager) - setElevationCompat(context.dpToPx(4f)) + with(pagerAdapter) { + containerId = binding.messageViewPager.id + titleFactory = { + when (it) { + 0 -> getString(R.string.message_inbox) + 1 -> getString(R.string.message_sent) + 2 -> getString(R.string.message_trash) + else -> throw IllegalStateException() + } + } + itemFactory = { + when (it) { + 0 -> MessageTabFragment.newInstance(RECEIVED) + 1 -> MessageTabFragment.newInstance(SENT) + 2 -> MessageTabFragment.newInstance(TRASHED) + else -> throw IllegalStateException() + } + } + TabLayoutMediator(binding.messageTabLayout, binding.messageViewPager, this).attach() } - openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } + binding.messageTabLayout.elevation = requireContext().dpToPx(4f) + binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } } override fun showContent(show: Boolean) { - messageViewPager.visibility = if (show) VISIBLE else INVISIBLE - messageTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + messageViewPager.visibility = if (show) VISIBLE else INVISIBLE + messageTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } } override fun showProgress(show: Boolean) { - messageProgress.visibility = if (show) VISIBLE else INVISIBLE + binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE } - fun onDeleteMessage(message: Message) { - presenter.onDeleteMessage(message) + override fun showMessage(messageId: Int) { + showMessage(getString(messageId)) + } + + override fun showNewMessage(show: Boolean) { + binding.openSendMessageButton.run { + if (show) show() else hide() + } + } + + override fun showTabLayout(show: Boolean) { + binding.messageTabLayout.isVisible = show + + with(binding.messageViewPager) { + isUserInputEnabled = show + updateLayoutParams { + updateMargins(top = if (show) requireContext().dpToPx(48f).toInt() else 0) + } + } + } + + fun onChildFragmentShowActionMode(show: Boolean) { + presenter.onChildViewShowActionMode(show) } fun onChildFragmentLoaded() { presenter.onChildViewLoaded() } - override fun notifyChildMessageDeleted(tabId: Int) { - (pagerAdapter.getFragmentInstance(tabId) as? MessageTabFragment)?.onParentDeleteMessage() + fun onChildFragmentShowNewMessage(show: Boolean) { + presenter.onChildViewShowNewMessage(show) + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun onFragmentChanged() { + if (::presenter.isInitialized) presenter.onFragmentChanged() } override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { - (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment)?.onParentLoadData(forceRefresh) + (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment) + ?.onParentLoadData(forceRefresh) + } + + override fun notifyChildrenFinishActionMode() { + repeat(3) { + (pagerAdapter.getFragmentInstance(it) as? MessageTabFragment) + ?.onParentFinishActionMode() + } + } + + override fun notifyChildParentReselected(index: Int) { + (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment) + ?.onParentReselected() } override fun openSendMessage() { context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) } } + override fun popView() { + (activity as? MainActivity)?.popView() + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt deleted file mode 100644 index 7e52233c..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageItem.kt +++ /dev/null @@ -1,67 +0,0 @@ -package io.github.wulkanowy.ui.modules.message - -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.NORMAL -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_message.* - -class MessageItem(val message: Message, private val noSubjectString: String) : - AbstractFlexibleItem() { - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun getLayoutRes() = R.layout.item_message - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - val style = if (message.unread) BOLD else NORMAL - - messageItemAuthor.run { - text = if (message.folderId == MessageFolder.SENT.id) message.recipient else message.sender - setTypeface(null, style) - } - messageItemSubject.run { - text = if (message.subject.isNotBlank()) message.subject else noSubjectString - setTypeface(null, style) - } - messageItemDate.run { - text = message.date.toFormattedString() - setTypeface(null, style) - } - with(messageItemAttachmentIcon) { - visibility = if (message.hasAttachments) View.VISIBLE else View.GONE - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MessageItem - - if (message != other.message) return false - return true - } - - override fun hashCode(): Int { - return message.hashCode() - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt deleted file mode 100644 index bf795020..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.ui.modules.message - -import dagger.Module -import dagger.Provides -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.di.scopes.PerChildFragment -import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter -import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment - -@Suppress("unused") -@Module -abstract class MessageModule { - - companion object { - - @PerFragment - @Provides - fun provideMessageAdapter(fragment: MessageFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager) - } - - @PerChildFragment - @ContributesAndroidInjector - abstract fun bindMessageTabFragment(): MessageTabFragment -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt index 0f5598b2..37a2d422 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt @@ -1,33 +1,40 @@ package io.github.wulkanowy.ui.modules.message -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class MessagePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, - studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: MessageView) { super.onAttachView(view) - disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) - .subscribe { - view.initView() - Timber.i("Message view was initialized") - loadData() - }) + presenterScope.launch { + view.initView() + Timber.i("Message view was initialized") + loadData() + } + + showIncognitoModeReminderMessage() + } + + private fun showIncognitoModeReminderMessage() { + if (preferencesRepository.isIncognitoMode) { + view?.showMessage(R.string.message_incognito_mode_on) + } } fun onPageSelected(index: Int) { loadChild(index) + view?.notifyChildrenFinishActionMode() } private fun loadData() { @@ -39,6 +46,18 @@ class MessagePresenter @Inject constructor( view?.notifyChildLoadData(index, forceRefresh) } + fun onFragmentChanged() { + view?.notifyChildrenFinishActionMode() + } + + fun onFragmentReselected() { + Timber.i("Message view is reselected") + view?.run { + popView() + notifyChildParentReselected(currentPageIndex) + } + } + fun onChildViewLoaded() { view?.apply { showContent(true) @@ -46,13 +65,12 @@ class MessagePresenter @Inject constructor( } } - fun onDeleteMessage(message: Message) { - view?.notifyChildMessageDeleted( - when (message.removed) { - true -> 2 - else -> message.folderId - 1 - } - ) + fun onChildViewShowNewMessage(show: Boolean) { + view?.showNewMessage(show) + } + + fun onChildViewShowActionMode(show: Boolean) { + view?.showTabLayout(!show) } fun onSendMessageButtonClicked() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt index 2aa4d78e..7fdc6e18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message +import androidx.annotation.StringRes import io.github.wulkanowy.ui.base.BaseView interface MessageView : BaseView { @@ -12,9 +13,19 @@ interface MessageView : BaseView { fun showProgress(show: Boolean) + fun showMessage(@StringRes messageId: Int) + + fun showNewMessage(show: Boolean) + + fun showTabLayout(show: Boolean) + fun notifyChildLoadData(index: Int, forceRefresh: Boolean) - fun notifyChildMessageDeleted(tabId: Int) + fun notifyChildrenFinishActionMode() + + fun notifyChildParentReselected(index: Int) fun openSendMessage() + + fun popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt new file mode 100644 index 00000000..59f6d288 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt @@ -0,0 +1,81 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil.ItemCallback +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.databinding.ItemMailboxChooserBinding +import javax.inject.Inject + +class MailboxChooserAdapter @Inject constructor() : + ListAdapter(Differ) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMailboxChooserBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class ItemViewHolder( + private val binding: ItemMailboxChooserBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: MailboxChooserItem) { + with(binding) { + mailboxItemName.text = item.mailbox?.getFirstLine() + ?: root.resources.getString(R.string.message_chip_all_mailboxes) + mailboxItemSchool.text = item.mailbox?.getSecondLine() + mailboxItemSchool.isVisible = !item.isAll + + root.setOnClickListener { item.onClickListener(item.mailbox) } + } + } + + private fun Mailbox.getFirstLine() = buildString { + if (studentName.isNotBlank() && studentName != userName) { + append(studentName) + append(" - ") + } + append(userName) + } + + private fun Mailbox.getSecondLine() = buildString { + append(schoolNameShort) + append(" - ") + append(getMailboxType(type)) + } + + private fun getMailboxType(type: MailboxType): String = when (type) { + MailboxType.STUDENT -> R.string.message_mailbox_type_student + MailboxType.PARENT -> R.string.message_mailbox_type_parent + MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian + MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee + MailboxType.UNKNOWN -> null + }.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() } + } + + private object Differ : ItemCallback() { + override fun areItemsTheSame( + oldItem: MailboxChooserItem, + newItem: MailboxChooserItem + ): Boolean { + return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey + } + + override fun areContentsTheSame( + oldItem: MailboxChooserItem, + newItem: MailboxChooserItem + ): Boolean { + return oldItem == newItem + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt new file mode 100644 index 00000000..11d3c6c1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -0,0 +1,74 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.databinding.DialogMailboxChooserBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.parcelableArray +import javax.inject.Inject + +@AndroidEntryPoint +class MailboxChooserDialog : BaseDialogFragment(), MailboxChooserView { + + @Inject + lateinit var presenter: MailboxChooserPresenter + + @Inject + lateinit var mailboxAdapter: MailboxChooserAdapter + + companion object { + const val LISTENER_KEY = "mailbox_selected" + const val MAILBOX_KEY = "selected_mailbox" + const val REQUIRED_KEY = "is_mailbox_required" + + fun newInstance(mailboxes: List, isMailboxRequired: Boolean, folder: String) = + MailboxChooserDialog().apply { + arguments = bundleOf( + MAILBOX_KEY to mailboxes.toTypedArray(), + REQUIRED_KEY to isMailboxRequired, + LISTENER_KEY to folder, + ) + } + } + + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMailboxChooserBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView( + view = this, + requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), + mailboxes = requireArguments().parcelableArray(MAILBOX_KEY).orEmpty().toList(), + ) + } + + override fun initView() { + binding.accountQuickDialogRecycler.adapter = mailboxAdapter + } + + override fun submitData(items: List) { + mailboxAdapter.submitList(items) + } + + override fun onMailboxSelected(item: Mailbox?) { + setFragmentResult( + requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(), + result = bundleOf(MAILBOX_KEY to item), + ) + dismiss() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt new file mode 100644 index 00000000..6923cf08 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox + +data class MailboxChooserItem( + val mailbox: Mailbox? = null, + val isAll: Boolean = false, + val onClickListener: (Mailbox?) -> Unit, +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt new file mode 100644 index 00000000..5bd7c84a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class MailboxChooserPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + fun onAttachView(view: MailboxChooserView, mailboxes: List, requireMailbox: Boolean) { + super.onAttachView(view) + + view.initView() + Timber.i("Mailbox chooser view was initialized") + view.submitData(getMailboxItems(mailboxes, requireMailbox)) + } + + private fun getMailboxItems( + mailboxes: List, + requireMailbox: Boolean, + ): List = buildList { + if (!requireMailbox) { + add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect)) + } + addAll(mailboxes.map { + MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect) + }) + } + + fun onMailboxSelect(item: Mailbox?) { + view?.onMailboxSelected(item) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt new file mode 100644 index 00000000..2e20ee81 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.ui.base.BaseView + +interface MailboxChooserView : BaseView { + + fun initView() + + fun submitData(items: List) + + fun onMailboxSelected(item: Mailbox?) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index a0ac6ec7..b83f7e23 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -1,19 +1,20 @@ package io.github.wulkanowy.ui.modules.message.preview -import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT +import androidx.core.text.parseAsHtml import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.databinding.ItemMessageAttachmentBinding +import io.github.wulkanowy.databinding.ItemMessageDividerBinding +import io.github.wulkanowy.databinding.ItemMessagePreviewBinding import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.item_message_attachment.view.* -import kotlinx.android.synthetic.main.item_message_preview.view.* import javax.inject.Inject class MessagePreviewAdapter @Inject constructor() : @@ -33,7 +34,8 @@ class MessagePreviewAdapter @Inject constructor() : private var attachments: List = emptyList() - override fun getItemCount() = if (messageWithAttachment == null) 0 else attachments.size + 1 + if (attachments.isNotEmpty()) 1 else 0 + override fun getItemCount() = + if (messageWithAttachment == null) 0 else attachments.size + 1 + if (attachments.isNotEmpty()) 1 else 0 override fun getItemViewType(position: Int) = when (position) { 0 -> ViewType.MESSAGE.id @@ -45,44 +47,84 @@ class MessagePreviewAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.MESSAGE.id -> MessageViewHolder(inflater.inflate(R.layout.item_message_preview, parent, false)) - ViewType.DIVIDER.id -> DividerViewHolder(inflater.inflate(R.layout.item_message_divider, parent, false)) - ViewType.ATTACHMENT.id -> AttachmentViewHolder(inflater.inflate(R.layout.item_message_attachment, parent, false)) + ViewType.MESSAGE.id -> MessageViewHolder( + ItemMessagePreviewBinding.inflate(inflater, parent, false) + ) + + ViewType.DIVIDER.id -> DividerViewHolder( + ItemMessageDividerBinding.inflate(inflater, parent, false) + ) + + ViewType.ATTACHMENT.id -> AttachmentViewHolder( + ItemMessageAttachmentBinding.inflate(inflater, parent, false) + ) + else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is MessageViewHolder -> bindMessage(holder.view, requireNotNull(messageWithAttachment).message) - is AttachmentViewHolder -> bindAttachment(holder.view, requireNotNull(messageWithAttachment).attachments[position - 2]) + is MessageViewHolder -> bindMessage( + holder, + requireNotNull(messageWithAttachment).message + ) + + is AttachmentViewHolder -> bindAttachment( + holder, + requireNotNull(messageWithAttachment).attachments[position - 2] + ) } } - @SuppressLint("SetTextI18n") - private fun bindMessage(view: View, message: Message) { - with(view) { - messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else context.getString(R.string.message_no_subject) - messagePreviewDate.text = context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) - messagePreviewContent.text = message.content - messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${context.getString(R.string.message_to)} ${message.recipient}" - else "${context.getString(R.string.message_from)} ${message.sender}" + private fun bindMessage(holder: MessageViewHolder, message: Message) { + val context = holder.binding.root.context + val recipientCount = (message.unreadBy ?: 0) + (message.readBy ?: 0) + val isReceived = message.unreadBy == null + + val readText = when { + recipientCount > 1 -> { + context.getString(R.string.message_read_by, message.readBy, recipientCount) + } + + message.readBy == 1 || (isReceived && !message.unread) -> { + context.getString(R.string.message_read, context.getString(R.string.all_yes)) + } + + else -> context.getString(R.string.message_read, context.getString(R.string.all_no)) + } + + with(holder.binding) { + messagePreviewSubject.text = message.subject.ifBlank { + context.getString(R.string.message_no_subject) + } + messagePreviewDate.text = context.getString( + R.string.message_date, + message.date.toFormattedString("yyyy-MM-dd HH:mm:ss") + ) + messagePreviewRead.text = readText + messagePreviewContent.text = message.content.parseAsHtml(FROM_HTML_MODE_COMPACT) + messagePreviewFromSender.text = message.sender + messagePreviewToRecipient.text = message.recipients } } - private fun bindAttachment(view: View, attachment: MessageAttachment) { - with(view) { + private fun bindAttachment(holder: AttachmentViewHolder, attachment: MessageAttachment) { + with(holder.binding) { messagePreviewAttachment.visibility = View.VISIBLE messagePreviewAttachment.text = attachment.filename - setOnClickListener { - context.openInternetBrowser(attachment.url) { } + root.setOnClickListener { + root.context.openInternetBrowser(attachment.url) { } } } } - class MessageViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class MessageViewHolder(val binding: ItemMessagePreviewBinding) : + RecyclerView.ViewHolder(binding.root) - class DividerViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class DividerViewHolder(val binding: ItemMessageDividerBinding) : + RecyclerView.ViewHolder(binding.root) - class AttachmentViewHolder(val view: View) : RecyclerView.ViewHolder(view) + class AttachmentViewHolder(val binding: ItemMessageAttachmentBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index 953238c0..8e7c7276 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -1,27 +1,38 @@ package io.github.wulkanowy.ui.modules.message.preview import android.os.Bundle -import android.view.LayoutInflater +import android.print.PrintAttributes +import android.print.PrintManager import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.annotation.StringRes +import androidx.core.content.getSystemService +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.databinding.FragmentMessagePreviewBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity -import kotlinx.android.synthetic.main.fragment_message_preview.* +import io.github.wulkanowy.utils.serializable +import io.github.wulkanowy.utils.shareText import javax.inject.Inject -class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.TitledView { +@AndroidEntryPoint +class MessagePreviewFragment : + BaseFragment(R.layout.fragment_message_preview), + MessagePreviewView, MainView.TitledView { @Inject lateinit var presenter: MessagePreviewPresenter @@ -33,43 +44,71 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl private var menuForwardButton: MenuItem? = null + private var menuRestoreButton: MenuItem? = null + private var menuDeleteButton: MenuItem? = null + private var menuDeleteForeverButton: MenuItem? = null + + private var menuShareButton: MenuItem? = null + + private var menuPrintButton: MenuItem? = null + + private var menuMuteButton: MenuItem? = null + override val titleStringId: Int get() = R.string.message_title override val deleteMessageSuccessString: String get() = getString(R.string.message_delete_success) - companion object { - const val MESSAGE_ID_KEY = "message_id" + override val muteMessageSuccessString: String + get() = getString(R.string.message_mute_success) - fun newInstance(message: Message): MessagePreviewFragment { - return MessagePreviewFragment().apply { - arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) } - } + override val unmuteMessageSuccessString: String + get() = getString(R.string.message_unmute_success) + + override val restoreMessageSuccessString: String + get() = getString(R.string.message_restore_success) + + override val messageNoSubjectString: String + get() = getString(R.string.message_no_subject) + + override val printHTML: String + get() = requireContext().assets.open("message-print-page.html").bufferedReader() + .use { it.readText() } + + override val messageNotExists: String + get() = getString(R.string.message_not_exists) + + companion object { + private const val MESSAGE_ARG_KEY = "message" + + fun newInstance(message: Message) = MessagePreviewFragment().apply { + arguments = bundleOf(MESSAGE_ARG_KEY to message) } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message_preview, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = messagePreviewContainer - presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessagePreviewBinding.bind(view) + messageContainer = binding.messagePreviewContainer + presenter.onAttachView( + view = this, + message = requireArguments().serializable(MESSAGE_ARG_KEY), + ) } override fun initView() { - messagePreviewErrorDetails.setOnClickListener { presenter.onDetailsClick() } + binding.messagePreviewErrorDetails.setOnClickListener { presenter.onDetailsClick() } - with(messagePreviewRecycler) { + with(binding.messagePreviewRecycler) { layoutManager = LinearLayoutManager(context) adapter = previewAdapter } @@ -79,15 +118,27 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl inflater.inflate(R.menu.action_menu_message_preview, menu) menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward) + menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore) menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) + menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever) + menuShareButton = menu.findItem(R.id.messagePreviewMenuShare) + menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint) + menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute) presenter.onCreateOptionsMenu() + + menu.findItem(R.id.mainMenuAccount).isVisible = false } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.messagePreviewMenuReply -> presenter.onReply() R.id.messagePreviewMenuForward -> presenter.onForward() + R.id.messagePreviewMenuRestore -> presenter.onMessageRestore() R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() + R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete() + R.id.messagePreviewMenuShare -> presenter.onShare() + R.id.messagePreviewMenuPrint -> presenter.onPrint() + R.id.messagePreviewMenuMute -> presenter.onMute() else -> false } } @@ -99,38 +150,44 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl } } + override fun updateMuteToggleButton(isMuted: Boolean) { + menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute) + + } + override fun showProgress(show: Boolean) { - messagePreviewProgress.visibility = if (show) VISIBLE else GONE + binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - messagePreviewRecycler.visibility = if (show) VISIBLE else GONE + binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE } - override fun showOptions(show: Boolean) { - menuReplyButton?.isVisible = show + override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) { + menuReplyButton?.isVisible = show && isReplayable menuForwardButton?.isVisible = show - menuDeleteButton?.isVisible = show - } - - override fun setDeletedOptionsLabels() { - menuDeleteButton?.setTitle(R.string.message_delete_forever) - } - - override fun setNotDeletedOptionsLabels() { - menuDeleteButton?.setTitle(R.string.message_move_to_bin) + menuRestoreButton?.isVisible = show && isRestorable + menuDeleteButton?.isVisible = show && !isRestorable + menuDeleteForeverButton?.isVisible = show && isRestorable + menuShareButton?.isVisible = show + menuPrintButton?.isVisible = show + menuMuteButton?.isVisible = show && isReplayable } override fun showErrorView(show: Boolean) { - messagePreviewError.visibility = if (show) VISIBLE else GONE + binding.messagePreviewError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - messagePreviewErrorMessage.text = message + binding.messagePreviewErrorMessage.text = message } override fun setErrorRetryCallback(callback: () -> Unit) { - messagePreviewErrorRetry.setOnClickListener { callback() } + binding.messagePreviewErrorRetry.setOnClickListener { callback() } + } + + override fun showMessage(@StringRes messageId: Int) { + showMessage(getString(messageId)) } override fun openMessageReply(message: Message?) { @@ -141,19 +198,41 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) } } + override fun shareText(text: String, subject: String) { + context?.shareText(text, subject) + } + + override fun printDocument(html: String, jobName: String) { + val webView = WebView(requireContext()) + webView.webViewClient = object : WebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = + false + + override fun onPageFinished(view: WebView, url: String) { + createWebPrintJob(view, jobName) + } + } + + webView.loadDataWithBaseURL("file:///android_asset/", html, "text/HTML", "UTF-8", null) + } + + private fun createWebPrintJob(webView: WebView, jobName: String) { + activity?.getSystemService()?.let { printManager -> + val printAdapter = webView.createPrintDocumentAdapter(jobName) + + printManager.print( + jobName, + printAdapter, + PrintAttributes.Builder().build() + ) + } + } + override fun popView() { (activity as MainActivity).popView() } - override fun notifyParentMessageDeleted(message: Message) { - parentFragmentManager.fragments.forEach { if (it is MessageFragment) it.onDeleteMessage(message) } - } - - override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(MESSAGE_ID_KEY, presenter.message) - super.onSaveInstanceState(outState) - } - override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 62ac5a53..3b3b2b42 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -1,35 +1,49 @@ package io.github.wulkanowy.ui.modules.message.preview +import android.annotation.SuppressLint +import androidx.core.text.parseAsHtml +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds class MessagePreviewPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val preferencesRepository: PreferencesRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { - var message: Message? = null + private var messageWithAttachments: MessageWithAttachment? = null private lateinit var lastError: Throwable private var retryCallback: () -> Unit = {} - fun onAttachView(view: MessagePreviewView, message: Message?) { + fun onAttachView(view: MessagePreviewView, message: Message) { super.onAttachView(view) view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError - this.message = message - loadData(requireNotNull(message)) + loadData(message) } private fun onMessageLoadRetry(message: Message) { @@ -44,73 +58,193 @@ class MessagePreviewPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - private fun loadData(message: Message) { - Timber.i("Loading message ${message.messageId} preview started") - disposable.apply { - clear() - add(studentRepository.getStudentById(message.studentId) - .flatMap { messageRepository.getMessage(it, message, true) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.showProgress(false) } - .subscribe({ message -> - Timber.i("Loading message ${message.message.messageId} preview result: Success ") - this@MessagePreviewPresenter.message = message.message - view?.apply { - setMessageWithAttachment(message) - initOptions() - } - analytics.logEvent("load_message_preview", "length" to message.message.content.length) - }) { - Timber.i("Loading message ${message.messageId} preview result: An exception occurred ") - retryCallback = { onMessageLoadRetry(message) } - errorHandler.dispatch(it) - }) + private fun loadData(messageToLoad: Message) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + messageRepository.getMessage( + student = student, + message = messageToLoad, + markAsRead = !preferencesRepository.isIncognitoMode, + ) } + .logResourceStatus("message ${messageToLoad.messageId} preview") + .onResourceData { + if (it != null) { + messageWithAttachments = it + view?.apply { + setMessageWithAttachment(it) + showContent(true) + initOptions() + updateMuteToggleButton(isMuted = it.mutedMessageSender != null) + if (preferencesRepository.isIncognitoMode && it.message.unread) { + showMessage(R.string.message_incognito_description) + } + } + } else { + delay(1.seconds) + view?.run { + showMessage(messageNotExists) + popView() + } + } + }.onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "message_preview", + "length" to it.message.content.length + ) + } + }.onResourceNotLoading { view?.showProgress(false) }.onResourceError { + retryCallback = { onMessageLoadRetry(messageToLoad) } + errorHandler.dispatch(it) + }.launch() } fun onReply(): Boolean { - return if (message != null) { - view?.openMessageReply(message) + return if (messageWithAttachments?.message != null) { + view?.openMessageReply(messageWithAttachments?.message) true } else false } fun onForward(): Boolean { - return if (message != null) { - view?.openMessageForward(message) + return if (messageWithAttachments?.message != null) { + view?.openMessageForward(messageWithAttachments?.message) true } else false } - private fun deleteMessage() { - message?.let { message -> - disposable.add(studentRepository.getCurrentStudent() - .flatMap { messageRepository.deleteMessage(it, message) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { - showContent(false) - showProgress(true) - showOptions(false) - showErrorView(false) - } + fun onShare(): Boolean { + val message = messageWithAttachments?.message ?: return false + val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() } + + val text = buildString { + appendLine("Temat: $subject") + appendLine("Od: ${message.sender}") + appendLine("Do: ${message.recipients}") + appendLine("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}") + + appendLine() + + appendLine(message.content.parseAsHtml()) + + if (!messageWithAttachments?.attachments.isNullOrEmpty()) { + appendLine() + appendLine("Załączniki:") + + append( + messageWithAttachments?.attachments.orEmpty() + .joinToString(separator = "\n") { attachment -> + "${attachment.filename}: ${attachment.url}" + }) + } + } + + view?.shareText( + subject = "FW: $subject", + text = text, + ) + return true + } + + @SuppressLint("NewApi") + fun onPrint(): Boolean { + val message = messageWithAttachments?.message ?: return false + val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() } + + val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss") + + val infoContent = buildString { + append("

Data wysłania

$dateString
") + + append("

Od

${message.sender}
") + append("

DO

${message.recipients}
") + } + val messageContent = "

${message.content}

".replace(Regex("[\\n\\r]{2,}"), "

") + .replace(Regex("[\\n\\r]"), "
") + + val jobName = buildString { + append("Wiadomość ") + append("od ${message.correspondents}") + append("do ${message.correspondents}") + append(" $dateString: $subject | Wulkanowy") + } + + view?.apply { + val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent) + .replace("%INFO%", infoContent) + printDocument(html, jobName) + } + + return true + } + + private fun restoreMessage() { + val message = messageWithAttachments?.message ?: return + + view?.run { + showContent(false) + showProgress(true) + showOptions( + show = false, + isReplayable = false, + isRestorable = false, + ) + showErrorView(false) + } + Timber.i("Restore message ${message.messageGlobalKey}") + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(decryptPass = true) + val mailbox = messageRepository.getMailboxByStudent(student) + messageRepository.restoreMessages(student, mailbox, listOfNotNull(message)) + } + .onFailure { + retryCallback = { onMessageRestore() } + errorHandler.dispatch(it) } - .doFinally { - view?.showProgress(false) - } - .subscribe({ + .onSuccess { view?.run { - notifyParentMessageDeleted(message) - showMessage(deleteMessageSuccessString) + showMessage(restoreMessageSuccessString) popView() } - }, { error -> - retryCallback = { onMessageDelete() } - errorHandler.dispatch(error) - }) + } + view?.showProgress(false) + } + } + + private fun deleteMessage() { + messageWithAttachments?.message ?: return + + view?.run { + showContent(false) + showProgress(true) + showOptions( + show = false, + isReplayable = false, + isRestorable = false, ) + showErrorView(false) + } + + Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}") + + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(decryptPass = true) + messageRepository.deleteMessage(student, messageWithAttachments?.message!!) + }.onFailure { + retryCallback = { onMessageDelete() } + errorHandler.dispatch(it) + }.onSuccess { + view?.run { + showMessage(deleteMessageSuccessString) + popView() + } + } + + view?.showProgress(false) } } @@ -118,11 +252,17 @@ class MessagePreviewPresenter @Inject constructor( view?.run { lastError = error setErrorDetails(message) + showContent(false) showErrorView(true) setErrorRetryCallback { retryCallback() } } } + fun onMessageRestore(): Boolean { + restoreMessage() + return true + } + fun onMessageDelete(): Boolean { deleteMessage() return true @@ -130,18 +270,40 @@ class MessagePreviewPresenter @Inject constructor( private fun initOptions() { view?.apply { - showOptions(message != null) - message?.let { - when (it.removed) { - true -> setDeletedOptionsLabels() - false -> setNotDeletedOptionsLabels() - } - } - + showOptions( + show = messageWithAttachments?.message != null, + isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id, + isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id, + ) } } fun onCreateOptionsMenu() { initOptions() } + + fun onMute(): Boolean { + val message = messageWithAttachments?.message ?: return false + val isMuted = messageWithAttachments?.mutedMessageSender != null + + presenterScope.launch { + runCatching { + when (isMuted) { + true -> { + messageRepository.unmuteMessage(message.correspondents) + view?.run { showMessage(unmuteMessageSuccessString) } + } + + false -> { + messageRepository.muteMessage(message.correspondents) + view?.run { showMessage(muteMessageSuccessString) } + } + } + }.onFailure { + errorHandler.dispatch(it) + } + } + view?.updateMuteToggleButton(isMuted) + return true + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt index 3d620459..ee0b6ce0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.preview +import androidx.annotation.StringRes import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.ui.base.BaseView @@ -8,31 +9,45 @@ interface MessagePreviewView : BaseView { val deleteMessageSuccessString: String + val muteMessageSuccessString: String + + val unmuteMessageSuccessString: String + + val restoreMessageSuccessString: String + + val messageNoSubjectString: String + + val printHTML: String + + val messageNotExists: String + fun initView() fun setMessageWithAttachment(item: MessageWithAttachment) + fun updateMuteToggleButton(isMuted: Boolean) + fun showProgress(show: Boolean) fun showContent(show: Boolean) - fun notifyParentMessageDeleted(message: Message) - fun showErrorView(show: Boolean) fun setErrorDetails(message: String) fun setErrorRetryCallback(callback: () -> Unit) - fun showOptions(show: Boolean) - - fun setDeletedOptionsLabels() - - fun setNotDeletedOptionsLabels() + fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) fun openMessageReply(message: Message?) fun openMessageForward(message: Message?) + fun shareText(text: String, subject: String) + fun popView() + + fun printDocument(html: String, jobName: String) + + fun showMessage(@StringRes messageId: Int) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt index dd2d2b9b..bd14bc89 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt @@ -2,7 +2,9 @@ package io.github.wulkanowy.ui.modules.message.send import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.materialchipsinput.ChipItem +import kotlinx.serialization.Serializable +@Serializable data class RecipientChipItem( override val title: String, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 232b05d7..0ba82f1a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -1,27 +1,43 @@ package io.github.wulkanowy.ui.modules.message.send +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.graphics.Rect +import android.os.Build import android.os.Bundle +import android.text.Spanned import android.view.Menu import android.view.MenuItem import android.view.TouchDelegate import android.view.View.GONE import android.view.View.VISIBLE +import android.view.ViewGroup import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.core.text.parseAsHtml +import androidx.core.text.toHtml +import androidx.core.view.* +import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.nullableSerializable import io.github.wulkanowy.utils.showSoftInput -import kotlinx.android.synthetic.main.activity_send_message.* import javax.inject.Inject -class SendMessageActivity : BaseActivity(), SendMessageView { +@AndroidEntryPoint +class SendMessageActivity : BaseActivity(), + SendMessageView { @Inject override lateinit var presenter: SendMessagePresenter @@ -29,6 +45,8 @@ class SendMessageActivity : BaseActivity(), SendMessageVie companion object { private const val EXTRA_MESSAGE = "EXTRA_MESSAGE" + private const val EXTRA_REASON = "EXTRA_REASON" + private const val EXTRA_REPLY = "EXTRA_REPLY" fun getStartIntent(context: Context) = Intent(context, SendMessageActivity::class.java) @@ -38,20 +56,22 @@ class SendMessageActivity : BaseActivity(), SendMessageVie .putExtra(EXTRA_MESSAGE, message) .putExtra(EXTRA_REPLY, reply) } + + fun getStartIntent(context: Context, reason: String): Intent { + return getStartIntent(context) + .putExtra(EXTRA_REASON, reason) + } } override val isDropdownListVisible: Boolean - get() = sendMessageTo.isDropdownListVisible + get() = binding.sendMessageTo.isDropdownListVisible @Suppress("UNCHECKED_CAST") - override val formRecipientsData: List - get() = sendMessageTo.addedChipItems as List + override lateinit var formRecipientsData: List - override val formSubjectValue: String - get() = sendMessageSubject.text.toString() + override lateinit var formSubjectValue: String - override val formContentValue: String - get() = sendMessageMessageContent.text.toString() + override lateinit var formContentValue: String override val messageRequiredRecipients: String get() = getString(R.string.message_required_recipients) @@ -62,29 +82,101 @@ class SendMessageActivity : BaseActivity(), SendMessageVie override val messageSuccess: String get() = getString(R.string.message_send_successful) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_send_message) - setSupportActionBar(sendMessageToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - messageContainer = sendMessageContainer + override val mailboxStudent: String + get() = getString(R.string.message_mailbox_type_student) - presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean) + override val mailboxParent: String + get() = getString(R.string.message_mailbox_type_parent) + + override val mailboxGuardian: String + get() = getString(R.string.message_mailbox_type_guardian) + + override val mailboxEmployee: String + get() = getString(R.string.message_mailbox_type_employee) + + @Suppress("UNCHECKED_CAST") + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView( + ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root + ) + setSupportActionBar(binding.sendMessageToolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.sendAppBar.isLifted = true + } + initializeMessageContainer() + + messageContainer = binding.sendMessageContainer + + formRecipientsData = binding.sendMessageTo.addedChipItems as List + formSubjectValue = binding.sendMessageSubject.text.toString() + formContentValue = + binding.sendMessageMessageContent.text.toString().parseAsHtml().toString() + binding.sendMessageFrom.setOnClickListener { presenter.onOpenMailboxChooser() } + + presenter.onAttachView( + view = this, + reason = intent.nullableSerializable(EXTRA_REASON), + message = intent.nullableSerializable(EXTRA_MESSAGE), + reply = intent.nullableSerializable(EXTRA_REPLY) + ) + supportFragmentManager.setFragmentResultListener(LISTENER_KEY, this) { _, bundle -> + presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY)) + } } + @SuppressLint("ClickableViewAccessibility") override fun initView() { setUpExtendedHitArea() - sendMessageScroll.setOnTouchListener { _, _ -> presenter.onTouchScroll() } - sendMessageTo.onTextChangeListener = presenter::onRecipientsTextChange + with(binding) { + sendMessageScroll.setOnTouchListener { _, _ -> presenter.onTouchScroll() } + sendMessageTo.onChipAddListener = { onRecipientChange() } + sendMessageTo.onTextChangeListener = presenter::onRecipientsTextChange + sendMessageSubject.doOnTextChanged { text, _, _, _ -> onMessageSubjectChange(text) } + sendMessageMessageContent.doOnTextChanged { text, _, _, _ -> onMessageContentChange(text) } + } } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + private fun initializeMessageContainer() { + ViewCompat.setOnApplyWindowInsetsListener(binding.sendMessageScroll) { view, insets -> + val navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + + view.updateLayoutParams { + bottomMargin = if (imeInsets.bottom > navigationBarInsets.bottom) { + imeInsets.bottom + } else { + navigationBarInsets.bottom + } + } + WindowInsetsCompat.CONSUMED + } + } + + private fun onMessageSubjectChange(text: CharSequence?) { + formSubjectValue = text.toString() + presenter.onMessageContentChange() + } + + private fun onMessageContentChange(text: CharSequence?) { + formContentValue = (text as Spanned).toHtml() + presenter.onMessageContentChange() + } + + private fun onRecipientChange() { + presenter.onMessageContentChange() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.action_menu_send_message, menu) return true } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - return if (item?.itemId == R.id.sendMessageMenuSend) presenter.onSend() + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.sendMessageMenuSend) presenter.onSend() else false } @@ -92,28 +184,28 @@ class SendMessageActivity : BaseActivity(), SendMessageVie return presenter.onUpNavigate() } - override fun setReportingUnit(unit: ReportingUnit) { - sendMessageFrom.text = unit.senderName + override fun setMailbox(mailbox: String) { + binding.sendMessageFrom.text = mailbox } override fun setRecipients(recipients: List) { - sendMessageTo.filterableChipItems = recipients + binding.sendMessageTo.filterableChipItems = recipients } override fun setSelectedRecipients(recipients: List) { - sendMessageTo.addChips(recipients) + binding.sendMessageTo.addChips(recipients) } override fun showProgress(show: Boolean) { - sendMessageProgress.visibility = if (show) VISIBLE else GONE + binding.sendMessageProgress.visibility = if (show) VISIBLE else GONE } override fun showContent(show: Boolean) { - sendMessageContent.visibility = if (show) VISIBLE else GONE + binding.sendMessageContent.visibility = if (show) VISIBLE else GONE } override fun showEmpty(show: Boolean) { - sendMessageEmpty.visibility = if (show) VISIBLE else GONE + binding.sendMessageEmpty.visibility = if (show) VISIBLE else GONE } override fun showActionBar(show: Boolean) { @@ -121,11 +213,11 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override fun setSubject(subject: String) { - sendMessageSubject.setText(subject) + binding.sendMessageSubject.setText(subject) } override fun setContent(content: String) { - sendMessageMessageContent.setText(content) + binding.sendMessageMessageContent.setText(content.parseAsHtml()) } override fun showMessage(text: String) { @@ -137,15 +229,25 @@ class SendMessageActivity : BaseActivity(), SendMessageVie } override fun hideDropdownList() { - sendMessageTo.hideDropdownList() + binding.sendMessageTo.hideDropdownList() } override fun scrollToRecipients() { - sendMessageScroll.post { - sendMessageScroll.scrollTo(0, sendMessageTo.bottom - dpToPx(53f).toInt()) + with(binding.sendMessageScroll) { + post { + scrollTo(0, binding.sendMessageTo.bottom - dpToPx(53f).toInt()) + } } } + override fun showMailboxChooser(mailboxes: List) { + MailboxChooserDialog.newInstance( + mailboxes = mailboxes, + isMailboxRequired = true, + folder = LISTENER_KEY, + ).show(supportFragmentManager, "chooser") + } + override fun popView() { finish() } @@ -153,24 +255,46 @@ class SendMessageActivity : BaseActivity(), SendMessageVie private fun setUpExtendedHitArea() { fun extendHitArea() { val containerHitRect = Rect().apply { - sendMessageContent.getHitRect(this) + binding.sendMessageContent.getHitRect(this) } val contentHitRect = Rect().apply { - sendMessageMessageContent.getHitRect(this) + binding.sendMessageMessageContent.getHitRect(this) } contentHitRect.top = contentHitRect.bottom contentHitRect.bottom = containerHitRect.bottom - sendMessageContent.touchDelegate = TouchDelegate(contentHitRect, sendMessageMessageContent) + binding.sendMessageContent.touchDelegate = + TouchDelegate(contentHitRect, binding.sendMessageMessageContent) } - sendMessageMessageContent.post { - sendMessageMessageContent.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + with(binding.sendMessageMessageContent) { + post { + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> extendHitArea() } extendHitArea() } - extendHitArea() } } + + override fun showMessageBackupDialog() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.message_title) + .setMessage(presenter.getMessageBackupContent(presenter.getRecipientsNames())) + .setPositiveButton(R.string.all_yes) { _, _ -> presenter.restoreMessageParts() } + .setNegativeButton(R.string.all_no) { _, _ -> presenter.clearDraft() } + .show() + } + + @Suppress("UNCHECKED_CAST") + override fun clearDraft() { + formRecipientsData = binding.sendMessageTo.addedChipItems as List + presenter.clearDraft() + } + + override fun getMessageBackupDialogString() = + resources.getString(R.string.message_restore_dialog) + + override fun getMessageBackupDialogStringWithRecipients(recipients: String) = + resources.getString(R.string.message_restore_dialog_with_recipients, recipients) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index aec79f9d..6155baea 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -1,55 +1,81 @@ package io.github.wulkanowy.ui.modules.message.send +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.recipient.RecipientRepository -import io.github.wulkanowy.data.repositories.reportingunit.ReportingUnitRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.pojos.MessageDraft +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.toFormattedString -import io.reactivex.Completable +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject class SendMessagePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val semesterRepository: SemesterRepository, private val messageRepository: MessageRepository, - private val reportingUnitRepository: ReportingUnitRepository, private val recipientRepository: RecipientRepository, private val preferencesRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { - fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) { + private val messageUpdateChannel = Channel() + + private var message: Message? = null + private var isReplay: Boolean? = null + + private var mailboxes: List = emptyList() + private var selectedMailbox: Mailbox? = null + + fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) { super.onAttachView(view) view.initView() + initializeSubjectStream() + this.message = message + this.isReplay = reply + Timber.i("Send message view was initialized") loadData(message, reply) - view.apply { + with(view) { + if (messageRepository.draftMessage != null && reply == null) { + view.showMessageBackupDialog() + } + reason?.let { + setSubject("Usprawiedliwienie") + setContent(it) + } message?.let { - setSubject(when (reply) { - true -> "RE: " - else -> "FW: " - } + message.subject) + setSubject( + when (reply) { + true -> "RE: " + else -> "FW: " + } + message.subject + ) if (preferencesRepository.fillMessageContent || reply != true) { - setContent( - when (reply) { - true -> "\n\n" - else -> "" - } + when (message.sender.isNotEmpty()) { - true -> "Od: ${message.sender}\n" - false -> "Do: ${message.recipient}\n" - } + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}") + setContent(buildString { + if (reply == true) { + append("

") + } + + append("Od: ${message.sender}
") + append("Do: ${message.recipients}
") + append("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}

") + append(message.content) + }) } } } @@ -92,116 +118,209 @@ class SendMessagePresenter @Inject constructor( return false } - private fun loadData(message: Message?, reply: Boolean?) { - var reportingUnit: ReportingUnit? = null - var recipientChips: List = emptyList() - var selectedRecipientChips: List = emptyList() + fun onOpenMailboxChooser() { + view?.showMailboxChooser(mailboxes) + } - Timber.i("Loading recipients started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it).map { semester -> it to semester } } - .flatMapCompletable { (student, semester) -> - reportingUnitRepository.getReportingUnit(student, semester.unitId) - .doOnSuccess { reportingUnit = it } - .flatMap { recipientRepository.getRecipients(student, 2, it).toMaybe() } - .doOnSuccess { - Timber.i("Loading recipients result: Success, fetched %d recipients", it.size) - recipientChips = createChips(it) - } - .flatMapCompletable { - if (message == null || reply != true) Completable.complete() - else recipientRepository.getMessageRecipients(student, message) - .doOnSuccess { - Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", it.size) - selectedRecipientChips = createChips(it) - } - .ignoreElement() - } + fun onMailboxSelected(mailbox: Mailbox?) { + selectedMailbox = mailbox + + loadData(message, isReplay) + } + + private fun loadData(message: Message?, reply: Boolean?) { + resourceFlow { + val student = studentRepository.getCurrentStudent() + + if (selectedMailbox == null && mailboxes.isEmpty()) { + selectedMailbox = messageRepository.getMailboxByStudent(student) + mailboxes = messageRepository.getMailboxes(student, false).toFirstResult() + .dataOrNull.orEmpty() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { + + Timber.i("Loading recipients started") + val recipients = createChips( + recipients = recipientRepository.getRecipients( + student = student, + mailbox = selectedMailbox, + type = MailboxType.EMPLOYEE, + ) + ) + Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size) + + Timber.i("Loading message recipients started") + val messageRecipients = when { + message != null && reply == true -> recipientRepository.getMessageSender( + student = student, + message = message, + mailbox = selectedMailbox, + ) + else -> emptyList() + }.let { createChips(it) } + Timber.i( + "Loaded message recipients to reply result: Success, fetched %d recipients", + messageRecipients.size + ) + + recipients to messageRecipients + } + .logResourceStatus("load recipients") + .onResourceLoading { view?.run { showProgress(true) showContent(false) } } - .doFinally { view?.run { showProgress(false) } } - .subscribe({ - view?.run { - if (reportingUnit !== null) { - reportingUnit?.let { setReportingUnit(it) } - setRecipients(recipientChips) - if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips) - showContent(true) - } else { - Timber.i("Loading recipients result: Can't find the reporting unit") - view?.showEmpty(true) - } - } - }, { - Timber.i("Loading recipients result: An exception occurred") + .onResourceNotLoading { + view?.run { showProgress(false) } + } + .onResourceError { view?.showContent(true) errorHandler.dispatch(it) - })) + } + .onResourceSuccess { + it.let { (recipientChips, selectedRecipientChips) -> + view?.run { + setMailbox(getMailboxName(selectedMailbox)) + setRecipients(recipientChips) + if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( + selectedRecipientChips + ) + showContent(true) + } + } + } + .launch() } private fun sendMessage(subject: String, content: String, recipients: List) { - Timber.i("Sending message started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { messageRepository.sendMessage(it, subject, content, recipients) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { + val mailbox = selectedMailbox ?: return + + resourceFlow { + val student = studentRepository.getCurrentStudent() + messageRepository.sendMessage( + student = student, + subject = subject, + content = content, + recipients = recipients, + mailbox = mailbox, + ) + }.logResourceStatus("sending message").onEach { + when (it) { + is Resource.Loading -> view?.run { showSoftInput(false) showContent(false) showProgress(true) showActionBar(false) } + is Resource.Success -> { + view?.clearDraft() + view?.run { + showMessage(messageSuccess) + popView() + } + analytics.logEvent("send_message", "recipients" to recipients.size) + } + is Resource.Error -> { + view?.run { + showContent(true) + showProgress(false) + showActionBar(true) + } + errorHandler.dispatch(it.error) + } } - .subscribe({ - Timber.i("Sending message result: Success") - analytics.logEvent("send_message", "recipients" to recipients.size) - view?.run { - showMessage(messageSuccess) - popView() - } - }, { - Timber.i("Sending message result: An exception occurred") - view?.run { - showContent(true) - showProgress(false) - showActionBar(true) - } - errorHandler.dispatch(it) - }) - ) + }.launch("send") } private fun createChips(recipients: List): List { - fun generateCorrectSummary(recipientRealName: String): String { - val substring = recipientRealName.substringBeforeLast("-") - return when { - substring == recipientRealName -> recipientRealName - substring.indexOf("(") != -1 -> { - recipientRealName.indexOf("(") - .let { recipientRealName.substring(if (it != -1) it else 0) } - } - substring.indexOf("[") != -1 -> { - recipientRealName.indexOf("[") - .let { recipientRealName.substring(if (it != -1) it else 0) } - } - else -> recipientRealName.substringAfter("-") - }.trim() - } - return recipients.map { RecipientChipItem( - title = it.name, - summary = generateCorrectSummary(it.realName), + title = it.userName, + summary = buildString { + getMailboxType(it.type)?.let(::append) + if (isNotBlank()) append(" ") + + append("(${it.schoolShortName})") + }, recipient = it ) } } + + private fun getMailboxName(mailbox: Mailbox?): String { + mailbox ?: return "" + + // username - accountType [\n student name - ] (school short name) + return buildString { + append(mailbox.userName) + append(" - ") + append(getMailboxType(mailbox.type)) + appendLine() + + if (mailbox.type == MailboxType.PARENT) { + append(mailbox.studentName) + append(" - ") + } + + append("(${mailbox.schoolNameShort})") + } + } + + private fun getMailboxType(type: MailboxType): String? = when (type) { + MailboxType.STUDENT -> view?.mailboxStudent + MailboxType.PARENT -> view?.mailboxParent + MailboxType.GUARDIAN -> view?.mailboxGuardian + MailboxType.EMPLOYEE -> view?.mailboxEmployee + MailboxType.UNKNOWN -> null + } + + fun onMessageContentChange() { + presenterScope.launch { + messageUpdateChannel.send(Unit) + } + } + + @OptIn(FlowPreview::class) + private fun initializeSubjectStream() { + presenterScope.launch { + messageUpdateChannel.consumeAsFlow() + .debounce(250) + .catch { Timber.e(it) } + .collect { + saveDraftMessage() + Timber.i("Draft message was saved!") + } + } + } + + private fun saveDraftMessage() { + messageRepository.draftMessage = MessageDraft( + recipients = view?.formRecipientsData!!, + subject = view?.formSubjectValue!!, + content = view?.formContentValue!!, + ) + } + + fun restoreMessageParts() { + val draftMessage = messageRepository.draftMessage ?: return + view?.setSelectedRecipients(draftMessage.recipients) + view?.setSubject(draftMessage.subject) + view?.setContent(draftMessage.content) + Timber.i("Continue work on draft") + } + + fun getRecipientsNames(): String { + return messageRepository.draftMessage?.recipients.orEmpty() + .joinToString { it.recipient.userName } + } + + fun clearDraft() { + messageRepository.draftMessage = null + Timber.i("Draft cleared!") + } + + fun getMessageBackupContent(recipients: String) = + if (recipients.isEmpty()) view?.getMessageBackupDialogString() + else view?.getMessageBackupDialogStringWithRecipients(recipients) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt index 2839f9ce..e27a09d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt @@ -1,17 +1,16 @@ package io.github.wulkanowy.ui.modules.message.send -import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.ui.base.BaseView interface SendMessageView : BaseView { - val isDropdownListVisible: Boolean - val formRecipientsData: List + var formRecipientsData: List - val formSubjectValue: String + var formSubjectValue: String - val formContentValue: String + var formContentValue: String val messageRequiredRecipients: String @@ -19,9 +18,17 @@ interface SendMessageView : BaseView { val messageSuccess: String + val mailboxStudent: String + + val mailboxParent: String + + val mailboxGuardian: String + + val mailboxEmployee: String + fun initView() - fun setReportingUnit(unit: ReportingUnit) + fun setMailbox(mailbox: String) fun setRecipients(recipients: List) @@ -46,4 +53,13 @@ interface SendMessageView : BaseView { fun scrollToRecipients() fun popView() + + fun showMessageBackupDialog() + + fun getMessageBackupDialogString(): String + + fun getMessageBackupDialogStringWithRecipients(recipients: String): String + + fun clearDraft() + fun showMailboxChooser(mailboxes: List) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt new file mode 100644 index 00000000..fadc77e6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -0,0 +1,194 @@ +package io.github.wulkanowy.ui.modules.message.tab + +import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.CompoundButton +import androidx.core.view.isVisible +import androidx.core.widget.ImageViewCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.ItemMessageBinding +import io.github.wulkanowy.databinding.ItemMessageChipsBinding +import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() { + + lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit + + lateinit var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit + + lateinit var onHeaderClickListener: (CompoundButton, Boolean) -> Unit + + lateinit var onMailboxClickListener: () -> Unit + + lateinit var onChangesDetectedListener: () -> Unit + + private var items = mutableListOf() + + fun submitData(data: List) { + val originalMessagesSize = items.count { it.viewType == MessageItemViewType.MESSAGE } + val newMessagesSize = data.count { it.viewType == MessageItemViewType.MESSAGE } + + if (originalMessagesSize != newMessagesSize) onChangesDetectedListener() + + val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data)) + items = data.toMutableList() + + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemViewType(position: Int) = items[position].viewType.ordinal + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (MessageItemViewType.entries[viewType]) { + MessageItemViewType.FILTERS -> HeaderViewHolder( + ItemMessageChipsBinding.inflate(inflater, parent, false) + ) + + MessageItemViewType.MESSAGE -> ItemViewHolder( + ItemMessageBinding.inflate(inflater, parent, false) + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is ItemViewHolder -> bindItemViewHolder(holder, position) + is HeaderViewHolder -> bindHeaderViewHolder(holder, position) + } + } + + @SuppressLint("PrivateResource") + private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) { + val item = items[position] as MessageTabDataItem.FilterHeader + val context = holder.binding.root.context + + with(holder.binding) { + chipMailbox.text = + item.selectedMailbox ?: context.getString(R.string.message_chip_all_mailboxes) + chipMailbox.chipBackgroundColor = ColorStateList.valueOf( + if (item.selectedMailbox == null) { + context.getCompatColor(R.color.m3_elevated_chip_background_color) + } else context.getThemeAttrColor(R.attr.colorPrimary, 64) + ) + chipMailbox.setTextColor( + if (item.selectedMailbox == null) { + context.getThemeAttrColor(R.attr.colorOnSurfaceVariant) + } else context.getThemeAttrColor(R.attr.colorPrimary) + ) + chipMailbox.setOnClickListener { onMailboxClickListener() } + + if (item.onlyUnread == null) { + chipUnread.isVisible = false + } else { + chipUnread.isVisible = true + chipUnread.isChecked = item.onlyUnread + chipUnread.setOnCheckedChangeListener(onHeaderClickListener) + } + chipUnread.isEnabled = item.isEnabled + + chipAttachments.isEnabled = item.isEnabled + chipAttachments.isChecked = item.onlyWithAttachments + chipAttachments.setOnCheckedChangeListener(onHeaderClickListener) + } + } + + private fun bindItemViewHolder(holder: ItemViewHolder, position: Int) { + val item = (items[position] as MessageTabDataItem.MessageItem) + val message = item.message + + with(holder.binding) { + val normalFont = Typeface.create("sans-serif", Typeface.NORMAL) + val boldFont = Typeface.create("sans-serif-black", Typeface.NORMAL) + + val primaryColor = root.context.getThemeAttrColor(android.R.attr.textColorPrimary) + val secondaryColor = root.context.getThemeAttrColor(android.R.attr.textColorSecondary) + + val currentFont = if (message.unread) boldFont else normalFont + val currentTextColor = if (message.unread) primaryColor else secondaryColor + + with(messageItemAuthor) { + text = message.correspondents + setTextColor(currentTextColor) + typeface = currentFont + } + with(messageItemSubject) { + text = message.subject.ifBlank { context.getString(R.string.message_no_subject) } + setTextColor(currentTextColor) + typeface = currentFont + } + with(messageItemDate) { + text = message.date.toFormattedString() + setTextColor(currentTextColor) + typeface = currentFont + } + with(messageItemAttachmentIcon) { + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor)) + isVisible = message.hasAttachments + } + messageItemUnreadIndicator.isVisible = message.unread || item.isMuted + + when (item.isMuted) { + true -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_notifications_off) + else -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_circle_notification) + } + + root.setOnClickListener { + holder.bindingAdapterPosition.let { + if (it != RecyclerView.NO_POSITION) { + onItemClickListener(item, it) + } + } + } + + root.setOnLongClickListener { + onLongItemClickListener(item) + true + } + + with(messageItemCheckbox) { + isChecked = item.isSelected + isVisible = item.isActionMode + } + } + } + + class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) + + class HeaderViewHolder(val binding: ItemMessageChipsBinding) : + RecyclerView.ViewHolder(binding.root) + + private class MessageTabDiffUtil( + private val old: List, private val new: List + ) : DiffUtil.Callback() { + + override fun getOldListSize(): Int = old.size + + override fun getNewListSize(): Int = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = old[oldItemPosition] + val newItem = new[newItemPosition] + + return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) { + oldItem.message.messageGlobalKey == newItem.message.messageGlobalKey + } else { + oldItem.viewType == newItem.viewType + } + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + old[oldItemPosition] == new[newItemPosition] + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt new file mode 100644 index 00000000..ef640e04 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt @@ -0,0 +1,22 @@ +package io.github.wulkanowy.ui.modules.message.tab + +import io.github.wulkanowy.data.db.entities.Message + +sealed class MessageTabDataItem(val viewType: MessageItemViewType) { + + data class MessageItem( + val message: Message, + val isMuted: Boolean, + val isSelected: Boolean, + val isActionMode: Boolean + ) : MessageTabDataItem(MessageItemViewType.MESSAGE) + + data class FilterHeader( + val selectedMailbox: String?, + val onlyUnread: Boolean?, + val onlyWithAttachments: Boolean, + val isEnabled: Boolean + ) : MessageTabDataItem(MessageItemViewType.FILTERS) +} + +enum class MessageItemViewType { FILTERS, MESSAGE } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index ce0fc780..12f9d323 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -1,120 +1,231 @@ package io.github.wulkanowy.ui.modules.message.tab import android.os.Bundle -import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import android.widget.CompoundButton +import androidx.annotation.StringRes +import androidx.appcompat.view.ActionMode +import androidx.appcompat.widget.SearchView +import androidx.core.os.bundleOf +import androidx.core.view.updatePadding +import androidx.fragment.app.setFragmentResultListener +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.databinding.FragmentMessageTabBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.message.MessageItem +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_message_tab.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.nullableSerializable import javax.inject.Inject -class MessageTabFragment : BaseFragment(), MessageTabView { +@AndroidEntryPoint +class MessageTabFragment : BaseFragment(R.layout.fragment_message_tab), + MessageTabView { @Inject lateinit var presenter: MessageTabPresenter @Inject - lateinit var tabAdapter: FlexibleAdapter> + lateinit var messageTabAdapter: MessageTabAdapter companion object { + const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" - fun newInstance(folder: MessageFolder): MessageTabFragment { - return MessageTabFragment().apply { - arguments = Bundle().apply { - putString(MESSAGE_TAB_FOLDER_ID, folder.name) - } - } + fun newInstance(folder: MessageFolder) = MessageTabFragment().apply { + arguments = bundleOf(MESSAGE_TAB_FOLDER_ID to folder.name) } } - override val noSubjectString: String - get() = getString(R.string.message_no_subject) - override val isViewEmpty - get() = tabAdapter.isEmpty + get() = messageTabAdapter.itemCount == 0 - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_message_tab, container, false) + private var actionMode: ActionMode? = null + + private val actionModeCallback = object : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + val inflater = mode.menuInflater + inflater.inflate(R.menu.context_menu_message_tab, menu) + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + val isTrashFolder = presenter.folder == MessageFolder.TRASHED + + menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder) + menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder) + menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder) + + return presenter.onPrepareActionMode() + } + + override fun onDestroyActionMode(mode: ActionMode) { + presenter.onDestroyActionMode() + actionMode = null + } + + override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean { + when (menu.itemId) { + R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete() + R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore() + R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete() + R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll() + } + return true + } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = messageTabRecycler - presenter.onAttachView(this, MessageFolder.valueOf( - (savedInstanceState ?: arguments)?.getString(MESSAGE_TAB_FOLDER_ID).orEmpty() - )) + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMessageTabBinding.bind(view) + messageContainer = binding.messageTabRecycler + + val folder = MessageFolder.valueOf( + (savedInstanceState ?: requireArguments()).getString(MESSAGE_TAB_FOLDER_ID).orEmpty() + ) + presenter.onAttachView(this, folder) } override fun initView() { - tabAdapter.setOnItemClickListener { presenter.onMessageItemSelected(it) } + with(messageTabAdapter) { + onItemClickListener = presenter::onMessageItemSelected + onLongItemClickListener = presenter::onMessageItemLongSelected + onHeaderClickListener = ::onChipChecked + onMailboxClickListener = presenter::onMailboxFilterSelected + onChangesDetectedListener = ::resetListPosition + } - messageTabRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) - adapter = tabAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) + with(binding.messageTabRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = messageTabAdapter + addItemDecoration(DividerItemDecoration(context, false)) + itemAnimator = null + } + + with(binding) { + messageTabSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + messageTabSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + messageTabSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + messageTabErrorRetry.setOnClickListener { presenter.onRetry() } + messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + + setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle -> + presenter.onMailboxSelected( + mailbox = bundle.nullableSerializable(MailboxChooserDialog.MAILBOX_KEY), ) } - messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - messageTabErrorRetry.setOnClickListener { presenter.onRetry() } - messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - override fun updateData(data: List) { - tabAdapter.updateDataSet(data) + @Deprecated("Deprecated in Java") + @Suppress("DEPRECATION") + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.action_menu_message_tab, menu) + + initializeSearchView(menu) } - override fun updateItem(item: AbstractFlexibleItem<*>) { - tabAdapter.updateItem(item) + private fun initializeSearchView(menu: Menu) { + val searchView = (menu.findItem(R.id.action_search).actionView as SearchView).apply { + queryHint = getString(R.string.all_search_hint) + maxWidth = Int.MAX_VALUE + } + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String) = false + override fun onQueryTextChange(query: String): Boolean { + presenter.onSearchQueryTextChange(query) + return true + } + }) } - override fun clearView() { - tabAdapter.clear() + override fun updateData(data: List) { + messageTabAdapter.submitData(data) + } + + override fun updateActionModeTitle(selectedMessagesSize: Int) { + actionMode?.title = resources.getQuantityString( + R.plurals.message_selected_messages_count, + selectedMessagesSize, + selectedMessagesSize + ) + } + + override fun updateSelectAllMenu(isAllSelected: Boolean) { + val menuItem = actionMode?.menu?.findItem(R.id.messageTabContextMenuSelectAll) ?: return + + if (isAllSelected) { + menuItem.setTitle(R.string.message_unselect_all) + menuItem.setIcon(R.drawable.ic_message_unselect_all) + } else { + menuItem.setTitle(R.string.message_select_all) + menuItem.setIcon(R.drawable.ic_message_select_all) + } } override fun showProgress(show: Boolean) { - messageTabProgress.visibility = if (show) VISIBLE else GONE + binding.messageTabProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - messageTabSwipe.isEnabled = enable + binding.messageTabSwipe.isEnabled = enable + } + + override fun resetListPosition() { + binding.messageTabRecycler.scrollToPosition(0) } override fun showContent(show: Boolean) { - messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE + binding.messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE } override fun showEmpty(show: Boolean) { - messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE + binding.messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE } override fun showErrorView(show: Boolean) { - messageTabError.visibility = if (show) VISIBLE else GONE + binding.messageTabError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - messageTabErrorMessage.text = message + binding.messageTabErrorMessage.text = message } override fun showRefresh(show: Boolean) { - messageTabSwipe.isRefreshing = show + binding.messageTabSwipe.isRefreshing = show + } + + override fun showMessage(@StringRes messageId: Int) { + showMessage(getString(messageId)) + } + + override fun notifyParentShowNewMessage(show: Boolean) { + (parentFragment as? MessageFragment)?.onChildFragmentShowNewMessage(show) } override fun openMessage(message: Message) { @@ -125,12 +236,55 @@ class MessageTabFragment : BaseFragment(), MessageTabView { (parentFragment as? MessageFragment)?.onChildFragmentLoaded() } + override fun notifyParentShowActionMode(show: Boolean) { + (parentFragment as? MessageFragment)?.onChildFragmentShowActionMode(show) + } + fun onParentLoadData(forceRefresh: Boolean) { presenter.onParentViewLoadData(forceRefresh) } - fun onParentDeleteMessage() { - presenter.onDeleteMessage() + fun onParentFinishActionMode() { + presenter.onParentFinishActionMode() + } + + fun onParentReselected() { + presenter.onParentReselected() + } + + private fun onChipChecked(chip: CompoundButton, isChecked: Boolean) { + when (chip.id) { + R.id.chip_unread -> presenter.onUnreadFilterSelected(isChecked) + R.id.chip_attachments -> presenter.onAttachmentsFilterSelected(isChecked) + } + } + + override fun showActionMode(show: Boolean) { + if (show) { + actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback) + } else { + actionMode?.finish() + } + } + + override fun showRecyclerBottomPadding(show: Boolean) { + binding.messageTabRecycler.updatePadding( + bottom = if (show) requireContext().dpToPx(64f).toInt() else 0 + ) + } + + override fun showMailboxChooser(mailboxes: List) { + (activity as? MainActivity)?.showDialogFragment( + MailboxChooserDialog.newInstance( + mailboxes = mailboxes, + isMailboxRequired = false, + folder = requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!, + ) + ) + } + + override fun hideKeyboard() { + activity?.hideSoftInput() } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index cbdb89b2..cda0b32b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,110 +1,299 @@ package io.github.wulkanowy.ui.modules.message.tab -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.message.MessageFolder -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.R +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.ui.modules.message.MessageItem -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber import javax.inject.Inject +import kotlin.math.pow class MessageTabPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper, +) : BasePresenter(errorHandler, studentRepository) { lateinit var folder: MessageFolder private lateinit var lastError: Throwable + private var lastSearchQuery = "" + + private var mailboxes: List = emptyList() + private var selectedMailbox: Mailbox? = null + + private var messages = emptyList() + + private val searchChannel = Channel() + + private val messagesToDelete = mutableSetOf() + + private var onlyUnread: Boolean? = false + + private var onlyWithAttachments = false + + private var isActionMode = false + fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() + initializeSearchStream() errorHandler.showErrorMessage = ::showErrorViewOnError this.folder = folder } fun onSwipeRefresh() { Timber.i("Force refreshing the $folder message") - onParentViewLoadData(true) + view?.run { loadData(true) } } fun onRetry() { view?.run { showErrorView(false) showProgress(true) + loadData(true) } - loadData(true) } fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } - fun onDeleteMessage() { - loadData(false) - } - fun onParentViewLoadData(forceRefresh: Boolean) { loadData(forceRefresh) } - fun onMessageItemSelected(item: AbstractFlexibleItem<*>) { - if (item is MessageItem) { - Timber.i("Select message ${item.message.id} item") - view?.run { - openMessage(item.message) - if (item.message.unread) { - item.message.unread = false - updateItem(item) - } + fun onParentFinishActionMode() { + view?.showActionMode(false) + } + + fun onParentReselected() { + view?.run { + if (!isViewEmpty) { + resetListPosition() } } } + fun onDestroyActionMode() { + isActionMode = false + messagesToDelete.clear() + updateDataInView() + + view?.run { + enableSwipe(true) + notifyParentShowNewMessage(true) + notifyParentShowActionMode(false) + showRecyclerBottomPadding(true) + } + } + + fun onPrepareActionMode(): Boolean { + isActionMode = true + messagesToDelete.clear() + updateDataInView() + + view?.apply { + enableSwipe(false) + notifyParentShowNewMessage(false) + notifyParentShowActionMode(true) + showRecyclerBottomPadding(false) + hideKeyboard() + } + return true + } + + fun onActionModeSelectRestore() { + Timber.i("Restore ${messagesToDelete.size} messages") + val messageList = messagesToDelete.toList() + + presenterScope.launch { + view?.run { + showProgress(true) + showContent(false) + showActionMode(false) + } + runCatching { + val student = studentRepository.getCurrentStudent(true) + messageRepository.restoreMessages(student, selectedMailbox, messageList) + } + .onFailure(errorHandler::dispatch) + .onSuccess { view?.showMessage(R.string.message_messages_restored) } + } + } + + fun onActionModeSelectDelete() { + Timber.i("Delete ${messagesToDelete.size} messages") + val messageList = messagesToDelete.toList() + + presenterScope.launch { + view?.run { + showProgress(true) + showContent(false) + showActionMode(false) + } + + runCatching { + val student = studentRepository.getCurrentStudent(true) + messageRepository.deleteMessages(student, messageList) + } + .onFailure(errorHandler::dispatch) + .onSuccess { view?.showMessage(R.string.message_messages_deleted) } + } + } + + fun onActionModeSelectCheckAll() { + val messagesToSelect = getFilteredData().map { it.message } + val isAllSelected = messagesToDelete.containsAll(messagesToSelect) + + if (isAllSelected) { + messagesToDelete.clear() + view?.showActionMode(false) + } else { + messagesToDelete.addAll(messagesToSelect) + updateDataInView() + } + + view?.run { + updateSelectAllMenu(!isAllSelected) + updateActionModeTitle(messagesToDelete.size) + } + } + + fun onMessageItemLongSelected(messageItem: MessageTabDataItem.MessageItem) { + if (!isActionMode) { + view?.showActionMode(true) + + messagesToDelete.add(messageItem.message) + + view?.updateActionModeTitle(messagesToDelete.size) + updateDataInView() + } + } + + fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) { + Timber.i("Select message ${messageItem.message.messageGlobalKey} item (position: $position)") + + if (!isActionMode) { + view?.run { + showActionMode(false) + openMessage(messageItem.message) + } + } else { + if (!messageItem.isSelected) { + messagesToDelete.add(messageItem.message) + } else { + messagesToDelete.remove(messageItem.message) + } + + if (messagesToDelete.isEmpty()) { + view?.showActionMode(false) + } + + val filteredData = getFilteredData().map { it.message } + + view?.run { + updateActionModeTitle(messagesToDelete.size) + updateSelectAllMenu(messagesToDelete.containsAll(filteredData)) + } + updateDataInView() + } + } + + fun onUnreadFilterSelected(isChecked: Boolean) { + view?.run { + onlyUnread = isChecked + loadData(false) + } + } + + fun onAttachmentsFilterSelected(isChecked: Boolean) { + view?.run { + onlyWithAttachments = isChecked + loadData(false) + } + } + + fun onMailboxFilterSelected() { + view?.showMailboxChooser(mailboxes) + } + + fun onMailboxSelected(mailbox: Mailbox?) { + selectedMailbox = mailbox + loadData(false) + } + private fun loadData(forceRefresh: Boolean) { Timber.i("Loading $folder message data started") - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student) - .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } - .map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() - } - } - .subscribe({ - Timber.i("Loading $folder message result: Success") - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - showErrorView(false) - updateData(it) - } - analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name) - }) { - Timber.i("Loading $folder message result: An exception occurred") - errorHandler.dispatch(it) - }) + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + + if (selectedMailbox == null && mailboxes.isEmpty()) { + selectedMailbox = messageRepository.getMailboxByStudent(student) + mailboxes = messageRepository.getMailboxes(student, forceRefresh).toFirstResult() + .dataOrNull.orEmpty() + } + + messageRepository.getMessages(student, selectedMailbox, folder, forceRefresh) } + .logResourceStatus("load $folder message") + .onResourceData { + messages = it + + val filteredData = getFilteredData() + + view?.run { + enableSwipe(true) + showErrorView(false) + showProgress(false) + showContent(true) + showEmpty(filteredData.isEmpty()) + } + + updateDataInView() + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.size, + "folder" to folder.name + ) + } + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + } + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -114,7 +303,130 @@ class MessageTabPresenter @Inject constructor( setErrorDetails(message) showErrorView(true) showEmpty(false) + showProgress(false) } else showError(message, error) } } + + fun onSearchQueryTextChange(query: String) { + presenterScope.launch { + searchChannel.send(query) + } + } + + @OptIn(FlowPreview::class) + private fun initializeSearchStream() { + presenterScope.launch { + searchChannel.consumeAsFlow() + .debounce(250) + .map { query -> + lastSearchQuery = query + + getFilteredData() + } + .catch { Timber.e(it) } + .collect { + Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}") + + view?.run { + showEmpty(it.isEmpty()) + showContent(true) + showErrorView(false) + } + + updateDataInView() + view?.resetListPosition() + } + } + } + + private fun getFilteredData(): List { + if (lastSearchQuery.trim().isEmpty()) { + val sortedMessages = messages.sortedByDescending { it.message.date } + return when { + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { + it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments + } + + (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread } + onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments } + else -> sortedMessages + } + } else { + val sortedMessages = messages + .map { it to calculateMatchRatio(it.message, lastSearchQuery) } + .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.message.date }) + .filter { it.second > 6000 } + .map { it.first } + return when { + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { + it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments + } + + (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread } + onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments } + else -> sortedMessages + } + } + } + + private fun updateDataInView() { + val data = getFilteredData() + + val list = buildList { + add( + MessageTabDataItem.FilterHeader( + onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT }, + onlyWithAttachments = onlyWithAttachments, + isEnabled = !isActionMode, + selectedMailbox = selectedMailbox?.let { + buildString { + if (it.studentName.isNotBlank() && it.studentName != it.userName) { + append(it.studentName) + append(" - ") + } + append(it.userName) + } + }, + ) + ) + + addAll(data.map { message -> + MessageTabDataItem.MessageItem( + message = message.message, + isMuted = message.mutedMessageSender != null, + isSelected = messagesToDelete.any { it.messageGlobalKey == message.message.messageGlobalKey }, + isActionMode = isActionMode + ) + }) + } + + view?.updateData(list) + } + + private fun calculateMatchRatio(message: Message, query: String): Int { + val subjectRatio = FuzzySearch.tokenSortPartialRatio(query.lowercase(), message.subject) + + val correspondentsRatio = FuzzySearch.tokenSortPartialRatio( + query.lowercase(), + message.correspondents + ) + + val dateRatio = listOf( + FuzzySearch.ratio( + query.lowercase(), + message.date.toFormattedString("dd.MM").lowercase() + ), + FuzzySearch.ratio( + query.lowercase(), + message.date.toFormattedString("dd.MM.yyyy").lowercase() + ) + ).maxOrNull() ?: 0 + + + return (subjectRatio.toDouble().pow(2) + + correspondentsRatio.toDouble().pow(2) + + dateRatio.toDouble().pow(2) * 2 + ).toInt() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt index 92115ed3..247af434 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt @@ -1,23 +1,23 @@ package io.github.wulkanowy.ui.modules.message.tab -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.annotation.StringRes +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.ui.base.BaseView -import io.github.wulkanowy.ui.modules.message.MessageItem interface MessageTabView : BaseView { - val noSubjectString: String - val isViewEmpty: Boolean fun initView() - fun updateData(data: List) + fun resetListPosition() - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateData(data: List) - fun clearView() + fun updateActionModeTitle(selectedMessagesSize: Int) + + fun updateSelectAllMenu(isAllSelected: Boolean) fun showProgress(show: Boolean) @@ -27,8 +27,12 @@ interface MessageTabView : BaseView { fun showEmpty(show: Boolean) + fun showMessage(@StringRes messageId: Int) + fun showErrorView(show: Boolean) + fun notifyParentShowNewMessage(show: Boolean) + fun setErrorDetails(message: String) fun showRefresh(show: Boolean) @@ -36,4 +40,14 @@ interface MessageTabView : BaseView { fun openMessage(message: Message) fun notifyParentDataLoaded() + + fun notifyParentShowActionMode(show: Boolean) + + fun hideKeyboard() + + fun showActionMode(show: Boolean) + + fun showRecyclerBottomPadding(show: Boolean) + + fun showMailboxChooser(mailboxes: List) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt index 27c72ce6..4bc3097d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceAdapter.kt @@ -1,10 +1,38 @@ package io.github.wulkanowy.ui.modules.mobiledevice -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.IFlexible +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.databinding.ItemMobileDeviceBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject -class MobileDeviceAdapter> : FlexibleAdapter(null, null, true) { +class MobileDeviceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = mutableListOf() var onDeviceUnregisterListener: (device: MobileDevice, position: Int) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMobileDeviceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val device = items[position] + + with(holder.binding) { + mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss") + mobileDeviceItemName.text = device.name + mobileDeviceItemUnregister.setOnClickListener { + onDeviceUnregisterListener(device, position) + } + } + } + + class ItemViewHolder(val binding: ItemMobileDeviceBinding) : + RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt index d47574f6..1afe773c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt @@ -1,31 +1,34 @@ package io.github.wulkanowy.ui.modules.mobiledevice import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.helpers.UndoHelper -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.core.view.postDelayed +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.databinding.FragmentMobileDeviceBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog -import kotlinx.android.synthetic.main.fragment_mobile_device.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledView { +@AndroidEntryPoint +class MobileDeviceFragment : + BaseFragment(R.layout.fragment_mobile_device), MobileDeviceView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MobileDevicePresenter @Inject - lateinit var devicesAdapter: MobileDeviceAdapter> + lateinit var devicesAdapter: MobileDeviceAdapter companion object { fun newInstance() = MobileDeviceFragment() @@ -35,98 +38,111 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi get() = R.string.mobile_devices_title override val isViewEmpty: Boolean - get() = devicesAdapter.isEmpty + get() = devicesAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_mobile_device, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = mobileDevicesRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMobileDeviceBinding.bind(view) + messageContainer = binding.mobileDevicesRecycler presenter.onAttachView(this) } override fun initView() { - with(mobileDevicesRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + devicesAdapter.onDeviceUnregisterListener = presenter::onUnregisterDevice + + with(binding.mobileDevicesRecycler) { + layoutManager = LinearLayoutManager(context) adapter = devicesAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } + + with(binding) { + mobileDevicesSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + mobileDevicesSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + mobileDevicesSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } + mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } + mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } + } + } + + override fun updateData(data: List) { with(devicesAdapter) { - isPermanentDelete = false - onDeviceUnregisterListener = presenter::onUnregisterDevice + items = data.toMutableList() + notifyDataSetChanged() } - mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } - mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } - mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } } - override fun updateData(data: List) { - devicesAdapter.updateDataSet(data) - } - - override fun restoreDeleteItem() { - devicesAdapter.restoreDeletedItems() - } - - override fun clearData() { - devicesAdapter.clear() - } - - override fun showUndo(position: Int, device: MobileDevice) { - val onActionListener = object : UndoHelper.OnActionListener { - override fun onActionConfirmed(action: Int, event: Int) { - presenter.onUnregisterConfirmed(device) - } - - override fun onActionCanceled(action: Int, positions: MutableList?) { - presenter.onUnregisterCancelled() - } + override fun deleteItem(device: MobileDevice, position: Int) { + with(devicesAdapter) { + items.removeAt(position) + notifyItemRemoved(position) + notifyItemRangeChanged(position, itemCount) } - - UndoHelper(devicesAdapter, onActionListener) - .withConsecutive(false) - .withAction(UndoHelper.Action.REMOVE) - .start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000) } - override fun hideRefresh() { - mobileDevicesSwipe.isRefreshing = false + override fun restoreDeleteItem(device: MobileDevice, position: Int) { + with(devicesAdapter) { + items.add(position, device) + notifyItemInserted(position) + notifyItemRangeChanged(position, itemCount) + } + } + + override fun showUndo(device: MobileDevice, position: Int) { + var confirmed = true + + Snackbar.make(binding.mobileDevicesRecycler, getString(R.string.mobile_device_removed), 3000) + .setAction(R.string.all_undo) { + confirmed = false + presenter.onUnregisterCancelled(device, position) + }.show() + + view?.postDelayed(3000) { + if (confirmed) presenter.onUnregisterConfirmed(device) + } + } + + override fun showRefresh(show: Boolean) { + binding.mobileDevicesSwipe.isRefreshing = show } override fun showProgress(show: Boolean) { - mobileDevicesProgress.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesProgress.visibility = if (show) VISIBLE else GONE } override fun showEmpty(show: Boolean) { - mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - mobileDevicesError.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - mobileDevicesErrorMessage.text = message + binding.mobileDevicesErrorMessage.text = message } override fun enableSwipe(enable: Boolean) { - mobileDevicesSwipe.isEnabled = enable + binding.mobileDevicesSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - mobileDevicesRecycler.visibility = if (show) VISIBLE else GONE + binding.mobileDevicesRecycler.visibility = if (show) VISIBLE else GONE } override fun showTokenDialog() { (activity as? MainActivity)?.showDialogFragment(MobileDeviceTokenDialog.newInstance()) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.mobileDevicesRecycler.smoothScrollToPosition(0) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt deleted file mode 100644 index 436c2d0e..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceItem.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.ui.modules.mobiledevice - -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_mobile_device.* - -class MobileDeviceItem(val device: MobileDevice) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_mobile_device - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss") - mobileDeviceItemName.text = device.name - mobileDeviceItemUnregister.setOnClickListener { - (adapter as MobileDeviceAdapter).onDeviceUnregisterListener(device, holder.flexibleAdapterPosition) - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MobileDeviceItem - - if (device.id != other.device.id) return false - return true - } - - override fun hashCode(): Int { - var result = device.hashCode() - result = 31 * result + device.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt deleted file mode 100644 index 59bbaa9f..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.wulkanowy.ui.modules.mobiledevice - -import dagger.Module -import dagger.Provides -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -@Module -class MobileDeviceModule { - - @Provides - fun provideMobileDeviceFlexibleAdapter() = MobileDeviceAdapter>() -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index a6a83f8a..56785dbf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -1,24 +1,23 @@ package io.github.wulkanowy.ui.modules.mobiledevice +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.MobileDeviceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject class MobileDevicePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val mobileDeviceRepository: MobileDeviceRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -48,34 +47,40 @@ class MobileDevicePresenter @Inject constructor( private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading mobile devices data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - mobileDeviceRepository.getDevices(student, semester, forceRefresh) - } - } - .map { items -> items.map { MobileDeviceItem(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.getDevices(student, semester, forceRefresh) + } + .logResourceStatus("load mobile devices data") + .onResourceData { view?.run { - hideRefresh() - showProgress(false) enableSwipe(true) - } - }.subscribe({ - Timber.i("Loading mobile devices result: Success") - view?.run { - updateData(it) + showProgress(false) + showErrorView(false) showContent(it.isNotEmpty()) showEmpty(it.isEmpty()) - showErrorView(false) + updateData(it) } - analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading mobile devices result: An exception occurred") - errorHandler.dispatch(it) - }) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "devices", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -95,47 +100,39 @@ class MobileDevicePresenter @Inject constructor( fun onUnregisterDevice(device: MobileDevice, position: Int) { view?.run { - showUndo(position, device) + deleteItem(device, position) + showUndo(device, position) showEmpty(isViewEmpty) } } - fun onUnregisterCancelled() { + fun onUnregisterCancelled(device: MobileDevice, position: Int) { view?.run { - restoreDeleteItem() + restoreDeleteItem(device, position) showEmpty(isViewEmpty) } } fun onUnregisterConfirmed(device: MobileDevice) { - Timber.i("Unregister device started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - mobileDeviceRepository.unregisterDevice(student, semester, device) - .flatMap { mobileDeviceRepository.getDevices(student, semester, it) } - } - } - .map { items -> items.map { MobileDeviceItem(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.unregisterDevice(student, semester, device) + } + .logResourceStatus("unregister device") + .onResourceSuccess { view?.run { showProgress(false) enableSwipe(true) } } - .subscribe({ - Timber.i("Unregister device result: Success") - view?.run { - updateData(it) - showContent(it.isNotEmpty()) - showEmpty(it.isEmpty()) - } - }) { - Timber.i("Unregister device result: An exception occurred") - errorHandler.dispatch(it) - } - ) + .onResourceError(errorHandler::dispatch) + .launch("unregister") + } + + fun onFragmentReselected() { + if (view?.isViewEmpty == false) { + view?.resetView() + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt index 869b59bb..973cb123 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt @@ -9,13 +9,15 @@ interface MobileDeviceView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) - fun restoreDeleteItem() + fun deleteItem(device: MobileDevice, position: Int) - fun hideRefresh() + fun restoreDeleteItem(device: MobileDevice, position: Int) - fun clearData() + fun showUndo(device: MobileDevice, position: Int) + + fun showRefresh(show: Boolean) fun showProgress(show: Boolean) @@ -29,7 +31,7 @@ interface MobileDeviceView : BaseView { fun setErrorDetails(message: String) - fun showUndo(position: Int, device: MobileDevice) - fun showTokenDialog() + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt index 8b81156b..2cc2a2aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt @@ -1,65 +1,68 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token +import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.graphics.BitmapFactory import android.os.Bundle import android.util.Base64 -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.Toast import androidx.core.content.getSystemService +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.databinding.DialogMobileDeviceBinding import io.github.wulkanowy.ui.base.BaseDialogFragment -import kotlinx.android.synthetic.main.dialog_mobile_device.* import javax.inject.Inject -class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { +@AndroidEntryPoint +class MobileDeviceTokenDialog : BaseDialogFragment(), + MobileDeviceTokenVIew { @Inject lateinit var presenter: MobileDeviceTokenPresenter companion object { - fun newInstance(): MobileDeviceTokenDialog = MobileDeviceTokenDialog() + + fun newInstance() = MobileDeviceTokenDialog() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMobileDeviceBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_mobile_device, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) } override fun initView() { - mobileDeviceDialogClose.setOnClickListener { dismiss() } + binding.mobileDeviceDialogClose.setOnClickListener { dismiss() } } override fun updateData(token: MobileDeviceToken) { - with(mobileDeviceDialogToken) { + with(binding.mobileDeviceDialogTokenValue) { text = token.token setOnClickListener { clickCopy(token.token) } } - with(mobileDeviceDialogSymbol) { + with(binding.mobileDeviceDialogSymbolValue) { text = token.symbol setOnClickListener { clickCopy(token.symbol) } } - with(mobileDeviceDialogPin) { + with(binding.mobileDeviceDialogPinValue) { text = token.pin setOnClickListener { clickCopy(token.pin) } } - mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let { + binding.mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let { BitmapFactory.decodeByteArray(it, 0, it.size) }) } @@ -71,11 +74,11 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew { } override fun hideLoading() { - mobileDeviceDialogProgress.visibility = GONE + binding.mobileDeviceDialogProgress.visibility = GONE } override fun showContent() { - mobileDeviceDialogContent.visibility = VISIBLE + binding.mobileDeviceDialogContent.visibility = VISIBLE } override fun closeDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index 6a2a6b98..875b73ad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -1,23 +1,22 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token -import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.repositories.MobileDeviceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject class MobileDeviceTokenPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val mobileDeviceRepository: MobileDeviceRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: MobileDeviceTokenVIew) { super.onAttachView(view) @@ -27,28 +26,29 @@ class MobileDeviceTokenPresenter @Inject constructor( } private fun loadData() { - Timber.i("Mobile device registration data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - mobileDeviceRepository.getToken(student, semester) - } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.hideLoading() } - .subscribe({ - Timber.i("Mobile device registration result: Success") + resourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.getToken(student, semester) + } + .logResourceStatus("load mobile device registration") + .onResourceData { view?.run { updateData(it) showContent() } - analytics.logEvent("device_register", "symbol" to it.token.substring(0, 3)) - }) { - Timber.i("Mobile device registration result: An exception occurred") + } + .onResourceSuccess { + analytics.logEvent( + "device_register", + "symbol" to it.token.substring(0, 3) + ) + } + .onResourceNotLoading { view?.hideLoading() } + .onResourceError { view?.closeDialog() errorHandler.dispatch(it) } - ) + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt new file mode 100644 index 00000000..23eaba7c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.modules.more + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemMoreBinding +import javax.inject.Inject + +class MoreAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (moreItem: MoreItem) -> Unit = {} + + var onLongClickListener: (moreItem: MoreItem) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMoreBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + val context = holder.binding.root.context + + with(holder.binding) { + moreItemTitle.text = context.getString(item.title) + moreItemImage.setImageResource(item.icon) + + root.setOnClickListener { onClickListener(item) } + root.setOnLongClickListener { + onLongClickListener(item) + true + } + } + } + + class ItemViewHolder(val binding: ItemMoreBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index ef9c36fa..27023d13 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -1,37 +1,31 @@ package io.github.wulkanowy.ui.modules.more -import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.FragmentMoreBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.modules.about.AboutFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import io.github.wulkanowy.ui.modules.settings.SettingsFragment -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_more.* import javax.inject.Inject -class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.MainChildView { +@AndroidEntryPoint +class MoreFragment : BaseFragment(R.layout.fragment_more), MoreView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MorePresenter @Inject - lateinit var moreAdapter: FlexibleAdapter> + lateinit var moreAdapter: MoreAdapter + + @Inject + lateinit var preferencesRepository: PreferencesRepository companion object { fun newInstance() = MoreFragment() @@ -40,44 +34,17 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai override val titleStringId: Int get() = R.string.more_title - override val messagesRes: Pair? - get() = context?.run { getString(R.string.message_title) to getCompatDrawable(R.drawable.ic_more_messages) } - - override val homeworkRes: Pair? - get() = context?.run { getString(R.string.homework_title) to getCompatDrawable(R.drawable.ic_more_homework) } - - override val noteRes: Pair? - get() = context?.run { getString(R.string.note_title) to getCompatDrawable(R.drawable.ic_more_note) } - - override val luckyNumberRes: Pair? - get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) } - - override val mobileDevicesRes: Pair? - get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) } - - override val schoolAndTeachersRes: Pair? - get() = context?.run { getString(R.string.schoolandteachers_title) to getCompatDrawable((R.drawable.ic_more_schoolandteachers)) } - - override val settingsRes: Pair? - get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) } - - override val aboutRes: Pair? - get() = context?.run { getString(R.string.about_title) to getCompatDrawable(R.drawable.ic_all_about) } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_more, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMoreBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - moreAdapter.setOnItemClickListener { presenter.onItemSelected(it) } + moreAdapter.onClickListener = presenter::onItemSelected - moreRecycler.apply { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.moreRecycler) { + layoutManager = LinearLayoutManager(context) adapter = moreAdapter } } @@ -86,48 +53,33 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai if (::presenter.isInitialized) presenter.onViewReselected() } + override fun onFragmentChanged() { + (parentFragmentManager.fragments.find { it is MessageFragment } as MessageFragment?) + ?.onFragmentChanged() + } + override fun updateData(data: List) { - moreAdapter.updateDataSet(data) - } - - override fun openMessagesView() { - (activity as? MainActivity)?.pushView(MessageFragment.newInstance()) - } - - override fun openHomeworkView() { - (activity as? MainActivity)?.pushView(HomeworkFragment.newInstance()) - } - - override fun openNoteView() { - (activity as? MainActivity)?.pushView(NoteFragment.newInstance()) - } - - override fun openLuckyNumberView() { - (activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance()) - } - - override fun openMobileDevicesView() { - (activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance()) - } - - override fun openSchoolAndTeachersView() { - (activity as? MainActivity)?.pushView(SchoolAndTeachersFragment.newInstance()) - } - - override fun openSettingsView() { - (activity as? MainActivity)?.pushView(SettingsFragment.newInstance()) - } - - override fun openAboutView() { - (activity as? MainActivity)?.pushView(AboutFragment.newInstance()) + with(moreAdapter) { + items = data + notifyDataSetChanged() + } } override fun popView(depth: Int) { (activity as? MainActivity)?.popView(depth) } + override fun openView(destination: Destination) { + (activity as? MainActivity)?.pushView(destination.destinationFragment) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() } + + override fun restartApp() { + startActivity(MainActivity.getStartIntent(requireContext())) + requireActivity().finishAffinity() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt index 85b604e7..d544f041 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt @@ -1,47 +1,12 @@ package io.github.wulkanowy.ui.modules.more -import android.graphics.drawable.Drawable -import android.view.View -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_more.* +import io.github.wulkanowy.ui.modules.Destination -class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFlexibleItem() { +data class MoreItem( - override fun getLayoutRes() = R.layout.item_more + val icon: Int, - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } + val title: Int, - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - moreItemTitle.text = title - moreItemImage.setImageDrawable(drawable) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MoreItem - - if (title != other.title) return false - - return true - } - - override fun hashCode(): Int { - return title.hashCode() - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} + val destination: Destination +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 096f89e9..0ebaf4c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -1,18 +1,23 @@ package io.github.wulkanowy.ui.modules.more -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.Destination import timber.log.Timber import javax.inject.Inject class MorePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, - studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { + studentRepository: StudentRepository, + preferencesRepository: PreferencesRepository +) : BasePresenter(errorHandler, studentRepository) { + + private val moreAppMenuItem = preferencesRepository.appMenuItemOrder + .sortedBy { it.order } + .drop(4) override fun onAttachView(view: MoreView) { super.onAttachView(view) @@ -21,21 +26,10 @@ class MorePresenter @Inject constructor( loadData() } - fun onItemSelected(item: AbstractFlexibleItem<*>?) { - if (item !is MoreItem) return - Timber.i("Select more item \"${item.title}\"") - view?.run { - when (item.title) { - messagesRes?.first -> openMessagesView() - homeworkRes?.first -> openHomeworkView() - noteRes?.first -> openNoteView() - luckyNumberRes?.first -> openLuckyNumberView() - mobileDevicesRes?.first -> openMobileDevicesView() - schoolAndTeachersRes?.first -> openSchoolAndTeachersView() - settingsRes?.first -> openSettingsView() - aboutRes?.first -> openAboutView() - } - } + fun onItemSelected(moreItem: MoreItem) { + Timber.i("Select more item \"${moreItem.destination.destinationType}\"") + + view?.openView(moreItem.destination) } fun onViewReselected() { @@ -45,17 +39,21 @@ class MorePresenter @Inject constructor( private fun loadData() { Timber.i("Load items for more view") - view?.run { - updateData(listOfNotNull( - messagesRes?.let { MoreItem(it.first, it.second) }, - homeworkRes?.let { MoreItem(it.first, it.second) }, - noteRes?.let { MoreItem(it.first, it.second) }, - luckyNumberRes?.let { MoreItem(it.first, it.second) }, - mobileDevicesRes?.let { MoreItem(it.first, it.second) }, - schoolAndTeachersRes?.let { MoreItem(it.first, it.second) }, - settingsRes?.let { MoreItem(it.first, it.second) }, - aboutRes?.let { MoreItem(it.first, it.second) }) + val moreItems = moreAppMenuItem.map { + MoreItem( + icon = it.icon, + title = it.title, + destination = it.destinationType.defaultDestination ) } + .plus( + MoreItem( + icon = R.drawable.ic_more_settings, + title = R.string.settings_title, + destination = Destination.Settings + ) + ) + + view?.updateData(moreItems) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt index 41008176..05b426d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt @@ -1,45 +1,17 @@ package io.github.wulkanowy.ui.modules.more -import android.graphics.drawable.Drawable import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.Destination interface MoreView : BaseView { - val messagesRes: Pair? - - val homeworkRes: Pair? - - val noteRes: Pair? - - val luckyNumberRes: Pair? - - val mobileDevicesRes: Pair? - - val schoolAndTeachersRes: Pair? - - val settingsRes: Pair? - - val aboutRes: Pair? - fun initView() fun updateData(data: List) - fun openSettingsView() - - fun openAboutView() - fun popView(depth: Int) - fun openMessagesView() + fun openView(destination: Destination) - fun openHomeworkView() - - fun openNoteView() - - fun openLuckyNumberView() - - fun openMobileDevicesView() - - fun openSchoolAndTeachersView() + fun restartApp() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt new file mode 100644 index 00000000..48482cb7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteAdapter.kt @@ -0,0 +1,60 @@ +package io.github.wulkanowy.ui.modules.note + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.ItemNoteBinding +import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class NoteAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = mutableListOf() + + var onClickListener: (Note, position: Int) -> Unit = { _, _ -> } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + with(noteItemDate) { + text = item.date.toFormattedString() + setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD) + } + with(noteItemType) { + text = item.category + setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD) + } + with(noteItemPoints) { + text = "${if (item.points > 0) "+" else ""}${item.points}" + visibility = if (item.isPointsShow) View.VISIBLE else View.GONE + setTextColor(when (NoteCategory.getByValue(item.categoryType)) { + NoteCategory.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive) + NoteCategory.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative) + else -> context.getThemeAttrColor(android.R.attr.textColorPrimary) + }) + } + noteItemTeacher.text = item.teacher + noteItemContent.text = item.content + + root.setOnClickListener { onClickListener(item, position) } + } + } + + class ItemViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt index 20ffea4e..0592e924 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt @@ -1,63 +1,77 @@ package io.github.wulkanowy.ui.modules.note import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.content.ContextCompat -import androidx.fragment.app.DialogFragment +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.DialogNoteBinding +import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_note.* -import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType -class NoteDialog : DialogFragment() { +@AndroidEntryPoint +class NoteDialog : BaseDialogFragment() { private lateinit var note: Note companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Note): NoteDialog { - return NoteDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } - } + fun newInstance(note: Note) = NoteDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to note) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - note = getSerializable(ARGUMENT_KEY) as Note - } + note = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_note, container, false) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogNoteBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } @SuppressLint("SetTextI18n") - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + noteDialogDateValue.text = note.date.toFormattedString() + noteDialogCategoryValue.text = note.category + noteDialogTeacherValue.text = note.teacher + noteDialogContentValue.text = note.content + } - noteDialogDate.text = note.date.toFormattedString() - noteDialogCategory.text = note.category - noteDialogTeacher.text = note.teacher - noteDialogContent.text = note.content if (note.isPointsShow) { - with(noteDialogPoints) { + with(binding.noteDialogPointsValue) { text = "${if (note.points > 0) "+" else ""}${note.points}" - setTextColor(when (CategoryType.getByValue(note.categoryType)) { - CategoryType.POSITIVE -> ContextCompat.getColor(requireContext(), R.color.note_positive) - CategoryType.NEGATIVE -> ContextCompat.getColor(requireContext(), R.color.note_negative) - else -> requireContext().getThemeAttrColor(android.R.attr.textColorPrimary) - }) + setTextColor( + when (NoteCategory.getByValue(note.categoryType)) { + NoteCategory.POSITIVE -> ContextCompat.getColor( + requireContext(), + R.color.note_positive + ) + NoteCategory.NEGATIVE -> ContextCompat.getColor( + requireContext(), + R.color.note_negative + ) + else -> requireContext().getThemeAttrColor(android.R.attr.textColorPrimary) + } + ) } } - noteDialogClose.setOnClickListener { dismiss() } + + binding.noteDialogClose.setOnClickListener { dismiss() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt index e5335e45..cb54b384 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt @@ -1,31 +1,30 @@ package io.github.wulkanowy.ui.modules.note import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.databinding.FragmentNoteBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_note.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { +@AndroidEntryPoint +class NoteFragment : BaseFragment(R.layout.fragment_note), NoteView, + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: NotePresenter @Inject - lateinit var noteAdapter: FlexibleAdapter> + lateinit var noteAdapter: NoteAdapter companion object { fun newInstance() = NoteFragment() @@ -35,77 +34,90 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView { get() = R.string.note_title override val isViewEmpty: Boolean - get() = noteAdapter.isEmpty + get() = noteAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_note, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentNoteBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - noteAdapter.run { - setOnItemClickListener { presenter.onNoteItemSelected(it) } - } + noteAdapter.onClickListener = presenter::onNoteItemSelected - noteRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.noteRecycler) { + layoutManager = LinearLayoutManager(context) adapter = noteAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) + } + with(binding) { + noteSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + noteSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + noteSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + noteErrorRetry.setOnClickListener { presenter.onRetry() } + noteErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - noteErrorRetry.setOnClickListener { presenter.onRetry() } - noteErrorDetails.setOnClickListener { presenter.onDetailsClick() } } override fun showNoteDialog(note: Note) { (activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note)) } - override fun updateData(data: List) { - noteAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(noteAdapter) { + items = data.toMutableList() + notifyDataSetChanged() + } } - override fun updateItem(item: AbstractFlexibleItem<*>) { - noteAdapter.updateItem(item) + override fun updateItem(item: Note, position: Int) { + with(noteAdapter) { + items[position] = item + notifyItemChanged(position) + } } override fun clearData() { - noteAdapter.clear() + with(noteAdapter) { + items = mutableListOf() + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { - noteEmpty.visibility = if (show) VISIBLE else GONE + binding.noteEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - noteError.visibility = if (show) VISIBLE else GONE + binding.noteError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - noteErrorMessage.text = message + binding.noteErrorMessage.text = message } override fun showProgress(show: Boolean) { - noteProgress.visibility = if (show) VISIBLE else GONE + binding.noteProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - noteSwipe.isEnabled = enable + binding.noteSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - noteRecycler.visibility = if (show) VISIBLE else GONE + binding.noteRecycler.visibility = if (show) VISIBLE else GONE } - override fun hideRefresh() { - noteSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.noteSwipe.isRefreshing = show + } + + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.noteRecycler.smoothScrollToPosition(0) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt deleted file mode 100644 index 53fe6fa9..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.wulkanowy.ui.modules.note - -import android.annotation.SuppressLint -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.NORMAL -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import androidx.core.content.ContextCompat -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_note.* - -class NoteItem(val note: Note) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_note - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - with(noteItemDate) { - text = note.date.toFormattedString() - setTypeface(null, if (note.isRead) NORMAL else BOLD) - } - with(noteItemType) { - text = note.category - setTypeface(null, if (note.isRead) NORMAL else BOLD) - } - with(noteItemPoints) { - text = "${if (note.points > 0) "+" else ""}${note.points}" - visibility = if (note.isPointsShow) VISIBLE else GONE - setTextColor(when(CategoryType.getByValue(note.categoryType)) { - CategoryType.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive) - CategoryType.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative) - else -> context.getThemeAttrColor(android.R.attr.textColorPrimary) - }) - } - noteItemTeacher.text = note.teacher - noteItemContent.text = note.content - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as NoteItem - - if (note != other.note) return false - if (note.id != other.note.id) return false - return true - } - - override fun hashCode(): Int { - var result = note.hashCode() - result = 31 * result + note.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 7acf37a4..59a13fbb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,25 +1,26 @@ package io.github.wulkanowy.ui.modules.note -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.repositories.note.NoteRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.NoteRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject class NotePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val noteRepository: NoteRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val preferencesRepository: PreferencesRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -49,34 +50,53 @@ class NotePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading note data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it).map { semester -> semester to it } } - .flatMap { noteRepository.getNotes(it.second, it.first, forceRefresh) } - .map { items -> items.map { NoteItem(it) } } - .map { items -> items.sortedByDescending { it.note.date } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + if (!preferencesRepository.showNotes) { + view?.run { + enableSwipe(false) + showEmpty(false) + showContent(false) + showErrorView(false) + showProgress(false) + showEmpty(true) + } + + return + } + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + noteRepository.getNotes(student, semester, forceRefresh) + } + .logResourceStatus("load note data") + .mapResourceData { it.sortedByDescending { note -> note.date } } + .onResourceData { view?.run { - hideRefresh() - showProgress(false) enableSwipe(true) - } - }.subscribe({ - Timber.i("Loading note result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) + showProgress(false) showErrorView(false) showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } - analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh) - }, { - Timber.i("Loading note result: An exception occurred") - errorHandler.dispatch(it) - }) - ) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "note", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -90,29 +110,36 @@ class NotePresenter @Inject constructor( } } - fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is NoteItem) { - Timber.i("Select note item ${item.note.id}") - view?.run { - showNoteDialog(item.note) - if (!item.note.isRead) { - item.note.isRead = true - updateItem(item) - updateNote(item.note) - } + fun onNoteItemSelected(note: Note, position: Int) { + Timber.i("Select note item ${note.id}") + view?.run { + showNoteDialog(note) + if (!note.isRead) { + note.isRead = true + updateItem(note, position) + updateNote(note) } } } private fun updateNote(note: Note) { - Timber.i("Attempt to update note ${note.id}") - disposable.add(noteRepository.updateNote(note) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ Timber.i("Update note result: Success") }) - { error -> - Timber.i("Update note result: An exception occurred") - errorHandler.dispatch(error) - }) + resourceFlow { noteRepository.updateNote(note) } + .onEach { + when (it) { + is Resource.Loading -> Timber.i("Attempt to update note ${note.id}") + is Resource.Success -> Timber.i("Update note result: Success") + is Resource.Error -> { + Timber.i("Update note result: An exception occurred") + errorHandler.dispatch(it.error) + } + } + } + .launch("update_note") + } + + fun onFragmentReselected() { + if (view?.isViewEmpty == false) { + view?.resetView() + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt index a9c9f4f2..b813b315 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.note -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.ui.base.BaseView @@ -10,9 +9,9 @@ interface NoteView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateItem(item: Note, position: Int) fun clearData() @@ -28,7 +27,9 @@ interface NoteView : BaseView { fun showContent(show: Boolean) - fun hideRefresh() + fun showRefresh(show: Boolean) fun showNoteDialog(note: Note) + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt new file mode 100644 index 00000000..0763d4fa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.ui.modules.notifications + +import android.os.Bundle +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentNotificationsBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.utils.openNotificationSettings + +@AndroidEntryPoint +class NotificationsFragment : + BaseFragment(R.layout.fragment_notifications) { + + private val permission = "android.permission.POST_NOTIFICATIONS" + + private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) { + if (it) { + navigateToFinish() + } else showSettingsDialog() + } + + companion object { + fun newInstance() = NotificationsFragment() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentNotificationsBinding.bind(view) + initView() + } + + private fun initView() { + with(binding) { + notificationsSkip.setOnClickListener { navigateToFinish() } + notificationsEnable.setOnClickListener { requestPermission() } + } + } + + private fun showSettingsDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.notifications_header_title) + .setMessage(R.string.notifications_header_description) + .setNegativeButton(R.string.notifications_skip) { dialog, _ -> + dialog.dismiss() + navigateToFinish() + } + .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> + requireActivity().openNotificationSettings() + } + .show() + } + + private fun requestPermission() { + requestPermissionLauncher.launch(permission) + } + + private fun navigateToFinish() { + (requireActivity() as LoginActivity).navigateToFinish() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt new file mode 100644 index 00000000..92c54f45 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.modules.notificationscenter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Notification +import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class NotificationsCenterAdapter @Inject constructor() : + ListAdapter(DiffUtilCallback()) { + + var onItemClickListener: (Notification) -> Unit = {} + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemNotificationsCenterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = getItem(position) + + with(holder.binding) { + notificationsCenterItemTitle.text = item.title + notificationsCenterItemContent.text = item.content + notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM") + notificationsCenterItemIcon.setImageResource(item.type.icon) + + root.setOnClickListener { onItemClickListener(item) } + } + } + + class ViewHolder(val binding: ItemNotificationsCenterBinding) : + RecyclerView.ViewHolder(binding.root) + + private class DiffUtilCallback : DiffUtil.ItemCallback() { + + override fun areContentsTheSame(oldItem: Notification, newItem: Notification) = + oldItem == newItem + + override fun areItemsTheSame(oldItem: Notification, newItem: Notification) = + oldItem.id == newItem.id + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt new file mode 100644 index 00000000..ca71910a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt @@ -0,0 +1,83 @@ +package io.github.wulkanowy.ui.modules.notificationscenter + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Notification +import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationsCenterFragment : + BaseFragment(R.layout.fragment_notifications_center), + NotificationsCenterView, MainView.TitledView { + + @Inject + lateinit var presenter: NotificationsCenterPresenter + + @Inject + lateinit var notificationsCenterAdapter: NotificationsCenterAdapter + + companion object { + + fun newInstance() = NotificationsCenterFragment() + } + + override val titleStringId: Int + get() = R.string.notifications_center_title + + override val isViewEmpty: Boolean + get() = notificationsCenterAdapter.itemCount == 0 + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentNotificationsCenterBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + notificationsCenterAdapter.onItemClickListener = { notification -> + (requireActivity() as MainActivity).pushView(notification.destination.destinationFragment) + } + + with(binding.notificationsCenterRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = notificationsCenterAdapter + } + } + + override fun updateData(data: List) { + notificationsCenterAdapter.submitList(data) + } + + override fun showEmpty(show: Boolean) { + binding.notificationsCenterEmpty.isVisible = show + } + + override fun showProgress(show: Boolean) { + binding.notificationsCenterProgress.isVisible = show + } + + override fun showContent(show: Boolean) { + binding.notificationsCenterRecycler.isVisible = show + } + + override fun showErrorView(show: Boolean) { + binding.notificationCenterError.isVisible = show + } + + override fun setErrorDetails(message: String) { + binding.notificationCenterErrorMessage.text = message + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt new file mode 100644 index 00000000..de42e567 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt @@ -0,0 +1,83 @@ +package io.github.wulkanowy.ui.modules.notificationscenter + +import io.github.wulkanowy.data.repositories.NotificationRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +class NotificationsCenterPresenter @Inject constructor( + private val notificationRepository: NotificationRepository, + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: NotificationsCenterView) { + super.onAttachView(view) + view.initView() + Timber.i("Notifications centre view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun loadData() { + Timber.i("Loading notifications data started") + + flow { + val studentId = studentRepository.getCurrentStudent(false).id + emitAll(notificationRepository.getNotifications(studentId)) + } + .map { notificationList -> notificationList.sortedByDescending { it.date } } + .catch { Timber.i("Loading notifications result: An exception occurred: `$it`") } + .onEach { + Timber.i("Loading notifications result: Success") + + if (it.isEmpty()) { + view?.run { + showContent(false) + showProgress(false) + showEmpty(true) + } + } else { + view?.run { + showContent(true) + showProgress(false) + showEmpty(false) + updateData(it) + } + } + } + .launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterView.kt new file mode 100644 index 00000000..1bfbe75e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterView.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.modules.notificationscenter + +import io.github.wulkanowy.data.db.entities.Notification +import io.github.wulkanowy.ui.base.BaseView + +interface NotificationsCenterView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun showProgress(show: Boolean) + + fun showEmpty(show: Boolean) + + fun showContent(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt index 5f9c0b9a..f4fa8e01 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.schoolandteachers import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.ViewGroup +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentSchoolandteachersBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.main.MainView @@ -14,16 +15,23 @@ import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment import io.github.wulkanowy.ui.modules.schoolandteachers.teacher.TeacherFragment import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.setOnSelectPageListener -import kotlinx.android.synthetic.main.fragment_schoolandteachers.* import javax.inject.Inject -class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainView.TitledView { +@AndroidEntryPoint +class SchoolAndTeachersFragment : + BaseFragment(R.layout.fragment_schoolandteachers), + SchoolAndTeachersView, MainView.TitledView { @Inject lateinit var presenter: SchoolAndTeachersPresenter - @Inject - lateinit var pagerAdapter: BaseFragmentPagerAdapter + private val pagerAdapter by lazy { + BaseFragmentPagerAdapter( + fragmentManager = childFragmentManager, + pagesCount = 2, + lifecycle = lifecycle, + ) + } companion object { fun newInstance() = SchoolAndTeachersFragment() @@ -31,45 +39,56 @@ class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainVie override val titleStringId: Int get() = R.string.schoolandteachers_title - override val currentPageIndex get() = schoolandteachersViewPager.currentItem + override val currentPageIndex get() = binding.schoolandteachersViewPager.currentItem - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_schoolandteachers, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSchoolandteachersBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - with(pagerAdapter) { - containerId = schoolandteachersViewPager.id - addFragmentsWithTitle(mapOf( - SchoolFragment.newInstance() to getString(R.string.school_title), - TeacherFragment.newInstance() to getString(R.string.teachers_title) - )) - } - - with(schoolandteachersViewPager) { + with(binding.schoolandteachersViewPager) { adapter = pagerAdapter offscreenPageLimit = 2 setOnSelectPageListener(presenter::onPageSelected) } - with(schoolandteachersTabLayout) { - setupWithViewPager(schoolandteachersViewPager) - setElevationCompat(context.dpToPx(4f)) + with(pagerAdapter) { + containerId = binding.schoolandteachersViewPager.id + titleFactory = { + when (it) { + 0 -> getString(R.string.school_title) + 1 -> getString(R.string.teachers_title) + else -> throw IllegalStateException() + } + } + itemFactory = { + when (it) { + 0 -> SchoolFragment.newInstance() + 1 -> TeacherFragment.newInstance() + else -> throw IllegalStateException() + } + } + TabLayoutMediator( + binding.schoolandteachersTabLayout, + binding.schoolandteachersViewPager, + this + ).attach() } + + binding.schoolandteachersTabLayout.elevation = requireContext().dpToPx(4f) } override fun showContent(show: Boolean) { - schoolandteachersViewPager.visibility = if (show) VISIBLE else INVISIBLE - schoolandteachersTabLayout.visibility = if (show) VISIBLE else INVISIBLE + with(binding) { + schoolandteachersViewPager.visibility = if (show) VISIBLE else INVISIBLE + schoolandteachersTabLayout.visibility = if (show) VISIBLE else INVISIBLE + } } override fun showProgress(show: Boolean) { - schoolandteachersProgress.visibility = if (show) VISIBLE else INVISIBLE + binding.schoolandteachersProgress.visibility = if (show) VISIBLE else INVISIBLE } fun onChildFragmentLoaded() { @@ -77,7 +96,8 @@ class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainVie } override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { - (pagerAdapter.getFragmentInstance(index) as? SchoolAndTeachersChildView)?.onParentLoadData(forceRefresh) + (pagerAdapter.getFragmentInstance(index) as? SchoolAndTeachersChildView) + ?.onParentLoadData(forceRefresh) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersModule.kt deleted file mode 100644 index 2087b0b4..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersModule.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.ui.modules.schoolandteachers - -import dagger.Module -import dagger.Provides -import dagger.android.ContributesAndroidInjector -import io.github.wulkanowy.di.scopes.PerChildFragment -import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter -import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.teacher.TeacherFragment - -@Suppress("unused") -@Module -abstract class SchoolAndTeachersModule { - - companion object { - - @PerFragment - @Provides - fun provideSchoolAndTeachersAdapter(fragment: SchoolAndTeachersFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager) - } - - @PerChildFragment - @ContributesAndroidInjector - abstract fun provideSchoolFragment(): SchoolFragment - - @PerChildFragment - @ContributesAndroidInjector - abstract fun provideTeacherFragment(): TeacherFragment -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt index 1856803c..43823d6b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt @@ -1,28 +1,25 @@ package io.github.wulkanowy.ui.modules.schoolandteachers -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class SchoolAndTeachersPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: SchoolAndTeachersView) { super.onAttachView(view) - disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread) - .subscribe { - view.initView() - Timber.i("Message view was initialized") - loadData() - }) + presenterScope.launch { + view.initView() + Timber.i("Message view was initialized") + loadData() + } } fun onPageSelected(index: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt index 5a7c4cad..fba2f040 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt @@ -1,89 +1,95 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.databinding.FragmentSchoolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment +import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.openDialer import io.github.wulkanowy.utils.openNavigation -import kotlinx.android.synthetic.main.fragment_school.* import javax.inject.Inject -class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAndTeachersChildView { +@AndroidEntryPoint +class SchoolFragment : BaseFragment(R.layout.fragment_school), SchoolView, + MainView.TitledView, SchoolAndTeachersChildView { @Inject lateinit var presenter: SchoolPresenter override val titleStringId get() = R.string.school_title - override val isViewEmpty get() = schoolName.text.isBlank() + override val isViewEmpty get() = binding.schoolName.text.isBlank() companion object { fun newInstance() = SchoolFragment() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_school, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSchoolBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - schoolErrorRetry.setOnClickListener { presenter.onRetry() } - schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + schoolSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + schoolSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + schoolSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + schoolErrorRetry.setOnClickListener { presenter.onRetry() } + schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() } - schoolAddressButton.setOnClickListener { presenter.onAddressSelected() } - schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() } + schoolAddressButton.setOnClickListener { presenter.onAddressSelected() } + schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() } + } } override fun updateData(data: School) { - schoolName.text = data.name - schoolAddress.text = data.address.ifBlank { "-" } - schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE - schoolTelephone.text = data.contact.ifBlank { "-" } - schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE - schoolHeadmaster.text = data.headmaster - schoolPedagogue.text = data.pedagogue + with(binding) { + val noDataString = getString(R.string.all_no_data) + schoolName.text = data.name.ifBlank { noDataString } + schoolAddress.text = data.address.ifBlank { noDataString } + schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE + schoolTelephone.text = data.contact.ifBlank { noDataString } + schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE + schoolHeadmaster.text = data.headmaster.ifBlank { noDataString } + schoolPedagogue.text = data.pedagogue.ifBlank { noDataString } + } } override fun showEmpty(show: Boolean) { - schoolEmpty.visibility = if (show) VISIBLE else GONE + binding.schoolEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - schoolError.visibility = if (show) VISIBLE else GONE + binding.schoolError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - schoolErrorMessage.text = message + binding.schoolErrorMessage.text = message } override fun showProgress(show: Boolean) { - schoolProgress.visibility = if (show) VISIBLE else GONE + binding.schoolProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - schoolSwipe.isEnabled = enable + binding.schoolSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - schoolContent.visibility = if (show) VISIBLE else GONE + binding.schoolContent.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - schoolSwipe.isRefreshing = false + binding.schoolSwipe.isRefreshing = false } override fun notifyParentDataLoaded() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index e2eb614d..262398b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -1,23 +1,23 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school -import io.github.wulkanowy.data.repositories.school.SchoolRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.repositories.SchoolRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import kotlinx.coroutines.flow.catch import timber.log.Timber import javax.inject.Inject class SchoolPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val schoolRepository: SchoolRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private var address: String? = null @@ -62,44 +62,48 @@ class SchoolPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading school info started") - disposable.add(studentRepository.getCurrentStudent() - .flatMapMaybe { student -> - semesterRepository.getCurrentSemester(student).flatMapMaybe { - schoolRepository.getSchoolInfo(student, it, forceRefresh) + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + schoolRepository.getSchoolInfo(student, semester, forceRefresh) + } + .logResourceStatus("load school info") + .onResourceData { + if (it != null) { + view?.run { + address = it.address.ifBlank { null } + contact = it.contact.ifBlank { null } + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else view?.run { + Timber.i("Loading school result: No school info found") + showContent(!isViewEmpty) + showEmpty(isViewEmpty) + showErrorView(false) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "school") + } + } + .onResourceNotLoading { view?.run { hideRefresh() showProgress(false) enableSwipe(true) notifyParentDataLoaded() } - }.subscribe({ - Timber.i("Loading teachers result: Success") - view?.run { - address = it.address.ifBlank { null } - contact = it.contact.ifBlank { null } - updateData(it) - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent("load_school", "force_refresh" to forceRefresh) - }, { - Timber.i("Loading school result: An exception occurred") + } + .onResourceError(errorHandler::dispatch) + .catch { errorHandler.dispatch(it) - }, { - Timber.i("Loading school result: No school info found") - view?.run { - showContent(!isViewEmpty) - showEmpty(isViewEmpty) - showErrorView(false) - } - })) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -110,6 +114,7 @@ class SchoolPresenter @Inject constructor( showErrorView(true) showEmpty(false) showContent(false) + showProgress(false) } else showError(message, error) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt new file mode 100644 index 00000000..8deeae05 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherAdapter.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.modules.schoolandteachers.teacher + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.databinding.ItemTeacherBinding +import javax.inject.Inject + +class TeacherAdapter @Inject constructor() : RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemTeacherBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val teacher = items[position] + + with(holder.binding) { + teacherItemName.text = teacher.name + teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else root.context.getString(R.string.teacher_no_subject) + if (teacher.shortName.isNotBlank()) { + teacherItemShortName.visibility = View.VISIBLE + teacherItemShortName.text = "[${teacher.shortName}]" + } else { + teacherItemShortName.visibility = View.GONE + } + } + } + + class ItemViewHolder(val binding: ItemTeacherBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt index b6bb9c10..b052a383 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt @@ -1,31 +1,31 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.databinding.FragmentTeacherBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import kotlinx.android.synthetic.main.fragment_teacher.* +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject -class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, - SchoolAndTeachersChildView { +@AndroidEntryPoint +class TeacherFragment : BaseFragment(R.layout.fragment_teacher), + TeacherView, MainView.TitledView, SchoolAndTeachersChildView { @Inject lateinit var presenter: TeacherPresenter @Inject - lateinit var teacherAdapter: FlexibleAdapter> + lateinit var teacherAdapter: TeacherAdapter companion object { fun newInstance() = TeacherFragment() @@ -37,69 +37,62 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, override val noSubjectString get() = getString(R.string.teacher_no_subject) override val isViewEmpty: Boolean - get() = teacherAdapter.isEmpty + get() = teacherAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_teacher, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTeacherBinding.bind(view) presenter.onAttachView(this) } override fun initView() { - teacherRecycler.run { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.teacherRecycler) { + layoutManager = LinearLayoutManager(context) adapter = teacherAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) + } + with(binding) { + teacherSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + teacherSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + teacherSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + teacherErrorRetry.setOnClickListener { presenter.onRetry() } + teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } - teacherErrorRetry.setOnClickListener { presenter.onRetry() } - teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } } - override fun updateData(data: List) { - teacherAdapter.updateDataSet(data, true) - } - - override fun updateItem(item: AbstractFlexibleItem<*>) { - teacherAdapter.updateItem(item) - } - - override fun clearData() { - teacherAdapter.clear() + override fun updateData(data: List) { + with(teacherAdapter) { + items = data + notifyDataSetChanged() + } } override fun showEmpty(show: Boolean) { - teacherEmpty.visibility = if (show) VISIBLE else GONE + binding.teacherEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - teacherError.visibility = if (show) VISIBLE else GONE + binding.teacherError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - teacherErrorMessage.text = message + binding.teacherErrorMessage.text = message } override fun showProgress(show: Boolean) { - teacherProgress.visibility = if (show) VISIBLE else GONE + binding.teacherProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - teacherSwipe.isEnabled = enable + binding.teacherSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - teacherRecycler.visibility = if (show) VISIBLE else GONE + binding.teacherRecycler.visibility = if (show) VISIBLE else GONE } override fun hideRefresh() { - teacherSwipe.isRefreshing = false + binding.teacherSwipe.isRefreshing = false } override fun notifyParentDataLoaded() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt deleted file mode 100644 index 36831774..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherItem.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.ui.modules.schoolandteachers.teacher - -import android.annotation.SuppressLint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Teacher -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_teacher.* - -class TeacherItem(val teacher: Teacher, private val noSubjectText: String) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_teacher - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): TeacherItem.ViewHolder { - return TeacherItem.ViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: TeacherItem.ViewHolder, position: Int, payloads: MutableList?) { - holder.apply { - teacherItemName.text = teacher.name - teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else noSubjectText - if (teacher.shortName.isNotBlank()) { - teacherItemShortName.visibility = VISIBLE - teacherItemShortName.text = "[${teacher.shortName}]" - } else { - teacherItemShortName.visibility = GONE - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TeacherItem - - if (teacher != other.teacher) return false - if (teacher.id != other.teacher.id) return false - return true - } - - override fun hashCode(): Int { - var result = teacher.hashCode() - result = 31 * result + teacher.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index c5b6cfd6..fef06328 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -1,23 +1,23 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.teacher.TeacherRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import kotlinx.coroutines.flow.catch import timber.log.Timber import javax.inject.Inject class TeacherPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val teacherRepository: TeacherRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable @@ -38,7 +38,7 @@ class TeacherPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(true) + loadData() } fun onDetailsClick() { @@ -50,37 +50,44 @@ class TeacherPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading teachers data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - teacherRepository.getTeachers(student, semester, forceRefresh) + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + teacherRepository.getTeachers(student, semester, forceRefresh) + } + .logResourceStatus("load teachers data") + .onResourceData { + view?.run { + updateData(it + .filter { item -> item.name.isNotBlank() } + .sortedBy { it.name } + ) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + showErrorView(false) } } - .map { it.filter { teacher -> teacher.name.isNotBlank() } } - .map { items -> items.map { TeacherItem(it, view?.noSubjectString.orEmpty()) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "teachers", + "items" to it.size + ) + } + .onResourceNotLoading { view?.run { hideRefresh() showProgress(false) enableSwipe(true) notifyParentDataLoaded() } - }.subscribe({ - Timber.i("Loading teachers result: Success") - view?.run { - updateData(it) - showContent(it.isNotEmpty()) - showEmpty(it.isEmpty()) - showErrorView(false) - } - analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading teachers result: An exception occurred") + } + .onResourceError(errorHandler::dispatch) + .catch { errorHandler.dispatch(it) - }) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -90,6 +97,7 @@ class TeacherPresenter @Inject constructor( setErrorDetails(message) showErrorView(true) showEmpty(false) + showProgress(false) } else showError(message, error) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt index b16be8ff..c655bfad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherView.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView @@ -12,14 +12,10 @@ interface TeacherView : BaseView, SchoolAndTeachersChildView { fun initView() - fun updateData(data: List) - - fun updateItem(item: AbstractFlexibleItem<*>) + fun updateData(data: List) fun hideRefresh() - fun clearData() - fun showProgress(show: Boolean) fun enableSwipe(enable: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt new file mode 100644 index 00000000..731488a9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.modules.schoolannouncement + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding +import io.github.wulkanowy.utils.parseUonetHtml +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class SchoolAnnouncementAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onItemClickListener: (SchoolAnnouncement) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemSchoolAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + schoolAnnouncementItemDate.text = item.date.toFormattedString() + schoolAnnouncementItemType.text = item.subject + schoolAnnouncementItemContent.text = item.content.parseUonetHtml() + with(schoolAnnouncementItemAuthor) { + text = item.author + isVisible = !item.author.isNullOrBlank() + } + + root.setOnClickListener { onItemClickListener(item) } + } + } + + class ViewHolder(val binding: ItemSchoolAnnouncementBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt new file mode 100644 index 00000000..c1c58441 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt @@ -0,0 +1,55 @@ +package io.github.wulkanowy.ui.modules.schoolannouncement + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.parseUonetHtml +import io.github.wulkanowy.utils.serializable +import io.github.wulkanowy.utils.toFormattedString + +@AndroidEntryPoint +class SchoolAnnouncementDialog : BaseDialogFragment() { + + private lateinit var announcement: SchoolAnnouncement + + companion object { + + private const val ARGUMENT_KEY = "item" + + fun newInstance(announcement: SchoolAnnouncement) = SchoolAnnouncementDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to announcement) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + announcement = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogSchoolAnnouncementBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + announcementDialogSubjectValue.text = announcement.subject + announcementDialogDateValue.text = announcement.date.toFormattedString() + announcementDialogDescriptionValue.text = announcement.content.parseUonetHtml() + + announcementDialogClose.setOnClickListener { dismiss() } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementFragment.kt new file mode 100644 index 00000000..baf2824b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementFragment.kt @@ -0,0 +1,113 @@ +package io.github.wulkanowy.ui.modules.schoolannouncement + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.databinding.FragmentSchoolAnnouncementBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +@AndroidEntryPoint +class SchoolAnnouncementFragment : + BaseFragment(R.layout.fragment_school_announcement), + SchoolAnnouncementView, MainView.TitledView { + + @Inject + lateinit var presenter: SchoolAnnouncementPresenter + + @Inject + lateinit var schoolAnnouncementAdapter: SchoolAnnouncementAdapter + + companion object { + fun newInstance() = SchoolAnnouncementFragment() + } + + override val titleStringId: Int + get() = R.string.school_announcement_title + + override val isViewEmpty: Boolean + get() = schoolAnnouncementAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSchoolAnnouncementBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.directorInformationRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = schoolAnnouncementAdapter.apply { + onItemClickListener = presenter::onItemClickListener + } + addItemDecoration(DividerItemDecoration(context)) + } + with(binding) { + directorInformationSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + directorInformationSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + directorInformationSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + directorInformationErrorRetry.setOnClickListener { presenter.onRetry() } + directorInformationErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun updateData(data: List) { + with(schoolAnnouncementAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(schoolAnnouncementAdapter) { + items = listOf() + notifyDataSetChanged() + } + } + + override fun showEmpty(show: Boolean) { + binding.directorInformationEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.directorInformationError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.directorInformationErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.directorInformationProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun enableSwipe(enable: Boolean) { + binding.directorInformationSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.directorInformationRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showRefresh(show: Boolean) { + binding.directorInformationSwipe.isRefreshing = show + } + + override fun openSchoolAnnouncementDialog(item: SchoolAnnouncement) { + (activity as? MainActivity)?.showDialogFragment(SchoolAnnouncementDialog.newInstance(item)) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt new file mode 100644 index 00000000..f77a8833 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt @@ -0,0 +1,95 @@ +package io.github.wulkanowy.ui.modules.schoolannouncement + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import timber.log.Timber +import javax.inject.Inject + +class SchoolAnnouncementPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val analytics: AnalyticsHelper, + private val schoolAnnouncementRepository: SchoolAnnouncementRepository, +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: SchoolAnnouncementView) { + super.onAttachView(view) + view.initView() + Timber.i("School announcement view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the School announcement") + loadData(true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(true) + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + fun onItemClickListener(item: SchoolAnnouncement) { + view?.openSchoolAnnouncementDialog(item) + } + + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) + } + .logResourceStatus("load school announcement") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceSuccess { + analytics.logEvent( + "load_school_announcement", + "items" to it.size + ) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch("load_data") + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementView.kt new file mode 100644 index 00000000..383d0f29 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementView.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.modules.schoolannouncement + +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.ui.base.BaseView + +interface SchoolAnnouncementView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun openSchoolAnnouncementDialog(item: SchoolAnnouncement) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) + + fun showRefresh(show: Boolean) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index fe0a9763..f4503fad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -1,131 +1,65 @@ package io.github.wulkanowy.ui.modules.settings -import android.content.Context import android.content.SharedPreferences import android.os.Bundle -import androidx.appcompat.app.AlertDialog +import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import com.yariksoffice.lingver.Lingver -import dagger.android.support.AndroidSupportInjection +import androidx.preference.PreferenceScreen +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.AppInfo +import timber.log.Timber import javax.inject.Inject +@AndroidEntryPoint class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, MainView.TitledView, SettingsView { - @Inject - lateinit var presenter: SettingsPresenter - - @Inject - lateinit var appInfo: AppInfo - - @Inject - lateinit var lingver: Lingver - companion object { + fun newInstance() = SettingsFragment() } override val titleStringId get() = R.string.settings_title - override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success) - - override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed) - - override fun onAttach(context: Context) { - AndroidSupportInjection.inject(this) - super.onAttach(context) - } - - override fun initView() { - findPreference(getString(R.string.pref_key_services_force_sync))?.run { - onPreferenceClickListener = Preference.OnPreferenceClickListener { - presenter.onSyncNowClicked() - true - } - } - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - presenter.onAttachView(this) - } + @Inject + lateinit var preferencesRepository: PreferencesRepository override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.scheme_preferences, rootKey) - findPreference(getString(R.string.pref_key_notification_debug))?.isVisible = appInfo.isDebug - } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - presenter.onSharedPreferenceChanged(key) - } + val prefScreen: PreferenceScreen? = findPreference("settings_preferences") + val prefDeveloper: Preference? = findPreference("mod_settings") - override fun recreateView() { - activity?.recreate() - } - - override fun updateLanguage(langCode: String) { - lingver.setLocale(requireContext(), langCode) - } - - override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { - findPreference(serviceEnablesKey)?.run { - summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" - isEnabled = !isHolidays + if (!preferencesRepository.developerMode && prefScreen != null && prefDeveloper != null) { + prefScreen.removePreference(prefDeveloper) } + + Timber.i("Settings view was initialized") } - override fun setSyncInProgress(inProgress: Boolean) { - if (activity == null || !isAdded) return - - findPreference(getString(R.string.pref_key_services_force_sync))?.run { - isEnabled = !inProgress - summary = if (inProgress) getString(R.string.pref_services_sync_in_progress) else "" - } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences, rootKey) } - override fun showError(text: String, error: Throwable) { - (activity as? BaseActivity<*>)?.showError(text, error) - } + override fun showError(text: String, error: Throwable) {} - override fun showMessage(text: String) { - (activity as? BaseActivity<*>)?.showMessage(text) - } + override fun showMessage(text: String) {} - override fun showExpiredDialog() { - (activity as? BaseActivity<*>)?.showExpiredDialog() - } + override fun showExpiredCredentialsDialog() {} - override fun openClearLoginView() { - (activity as? BaseActivity<*>)?.openClearLoginView() - } + override fun onCaptchaVerificationRequired(url: String?) = Unit - override fun showErrorDetailsDialog(error: Throwable) { - ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) - } + override fun showDecryptionFailedDialog() {} - override fun showForceSyncDialog() { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.pref_services_dialog_force_sync_title) - .setMessage(R.string.pref_services_dialog_force_sync_summary) - .setPositiveButton(android.R.string.ok) { _, _ -> presenter.onForceSyncDialogSubmit() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() - } + override fun openClearLoginView() {} - override fun onResume() { - super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) - } + override fun showErrorDetailsDialog(error: Throwable) {} - override fun onPause() { - super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) - } + override fun showChangePasswordSnackbar(redirectUrl: String) {} + + override fun showAuthDialog() {} } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt deleted file mode 100644 index c8545ac0..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ /dev/null @@ -1,88 +0,0 @@ -package io.github.wulkanowy.ui.modules.settings - -import androidx.work.WorkInfo -import com.chuckerteam.chucker.api.ChuckerCollector -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.services.sync.SyncManager -import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.isHolidays -import org.threeten.bp.LocalDate.now -import timber.log.Timber -import javax.inject.Inject - -class SettingsPresenter @Inject constructor( - schedulers: SchedulersProvider, - errorHandler: ErrorHandler, - studentRepository: StudentRepository, - private val preferencesRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper, - private val syncManager: SyncManager, - private val chuckerCollector: ChuckerCollector, - private val appInfo: AppInfo -) : BasePresenter(errorHandler, studentRepository, schedulers) { - - override fun onAttachView(view: SettingsView) { - super.onAttachView(view) - Timber.i("Settings view was initialized") - view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays) - view.initView() - } - - fun onSharedPreferenceChanged(key: String) { - Timber.i("Change settings $key") - - with(preferencesRepository) { - when (key) { - serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } - servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) - isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable - appThemeKey -> view?.recreateView() - appLanguageKey -> view?.run { - updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage) - recreateView() - } - else -> Unit - } - } - analytics.logEvent("setting_changed", "name" to key) - } - - fun onSyncNowClicked() { - view?.showForceSyncDialog() - } - - fun onForceSyncDialogSubmit() { - view?.run { - val successString = syncSuccessString - val failedString = syncFailedString - disposable.add(syncManager.startOneTimeSyncWorker() - .doOnSubscribe { - setSyncInProgress(true) - Timber.i("Setting sync now started") - analytics.logEvent("sync_now", "status" to "started") - } - .doFinally { setSyncInProgress(false) } - .subscribe({ workInfo -> - when (workInfo.state) { - WorkInfo.State.SUCCEEDED -> { - showMessage(successString) - analytics.logEvent("sync_now", "status" to "success") - } - WorkInfo.State.FAILED -> { - showError(failedString, Throwable(workInfo.outputData.getString("error"))) - analytics.logEvent("sync_now", "status" to "failed") - } - else -> Timber.d("Sync now state: ${workInfo.state}") - } - }, { - Timber.e(it, "Sync now failed") - }) - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt index 4a1b0c76..79f91bc5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt @@ -2,21 +2,4 @@ package io.github.wulkanowy.ui.modules.settings import io.github.wulkanowy.ui.base.BaseView -interface SettingsView : BaseView { - - val syncSuccessString: String - - val syncFailedString: String - - fun initView() - - fun recreateView() - - fun updateLanguage(langCode: String) - - fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) - - fun setSyncInProgress(inProgress: Boolean) - - fun showForceSyncDialog() -} +interface SettingsView : BaseView \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt new file mode 100644 index 00000000..3ef1a80a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -0,0 +1,86 @@ +package io.github.wulkanowy.ui.modules.settings.advanced + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.preference.PreferenceFragmentCompat +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.AppInfo +import javax.inject.Inject + +@AndroidEntryPoint +class AdvancedFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener, + MainView.TitledView, AdvancedView { + + @Inject + lateinit var presenter: AdvancedPresenter + + @Inject + lateinit var appInfo: AppInfo + + override val titleStringId get() = R.string.pref_settings_advanced_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences_advanced, rootKey) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + presenter.onSharedPreferenceChanged(key) + } + + override fun showError(text: String, error: Throwable) { + (activity as? BaseActivity<*, *>)?.showError(text, error) + } + + override fun showMessage(text: String) { + (activity as? BaseActivity<*, *>)?.showMessage(text) + } + + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun openClearLoginView() { + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showErrorDetailsDialog(error: Throwable) { + ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt new file mode 100644 index 00000000..4bc24594 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.ui.modules.settings.advanced + +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import timber.log.Timber +import javax.inject.Inject + +class AdvancedPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val analytics: AnalyticsHelper, +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: AdvancedView) { + super.onAttachView(view) + Timber.i("Settings advanced view was initialized") + } + + fun onSharedPreferenceChanged(key: String?) { + key ?: return + Timber.i("Change settings $key") + analytics.logEvent("setting_changed", "name" to key) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedView.kt new file mode 100644 index 00000000..90c0474e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedView.kt @@ -0,0 +1,5 @@ +package io.github.wulkanowy.ui.modules.settings.advanced + +import io.github.wulkanowy.ui.base.BaseView + +interface AdvancedView : BaseView diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt new file mode 100644 index 00000000..62544f83 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -0,0 +1,122 @@ +package io.github.wulkanowy.ui.modules.settings.appearance + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SeekBarPreference +import com.yariksoffice.lingver.Lingver +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.AppInfo +import javax.inject.Inject + +@AndroidEntryPoint +class AppearanceFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener, + MainView.TitledView, AppearanceView { + + @Inject + lateinit var presenter: AppearancePresenter + + @Inject + lateinit var appInfo: AppInfo + + @Inject + lateinit var lingver: Lingver + + override val titleStringId get() = R.string.pref_settings_appearance_title + + companion object { + fun withFocusedPreference(key: String) = AppearanceFragment().apply { + arguments = bundleOf(FOCUSED_KEY to key) + } + + private const val FOCUSED_KEY = "focusedKey" + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + arguments?.getString(FOCUSED_KEY)?.let { scrollToPreference(it) } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey) + val attendanceTargetPref = + findPreference(requireContext().getString(R.string.pref_key_attendance_target))!! + attendanceTargetPref.setOnPreferenceChangeListener { _, newValueObj -> + val newValue = (((newValueObj as Int).toDouble() + 2.5) / 5).toInt() * 5 + attendanceTargetPref.value = + newValue.coerceIn(attendanceTargetPref.min, attendanceTargetPref.max) + + false + } + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + presenter.onSharedPreferenceChanged(key) + } + + override fun recreateView() { + activity?.recreate() + } + + override fun updateLanguage(langCode: String) { + lingver.setLocale(requireContext(), langCode) + } + + override fun updateLanguageToFollowSystem() { + lingver.setFollowSystemLocale(requireContext()) + } + + override fun showError(text: String, error: Throwable) { + (activity as? BaseActivity<*, *>)?.showError(text, error) + } + + override fun showMessage(text: String) { + (activity as? BaseActivity<*, *>)?.showMessage(text) + } + + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun openClearLoginView() { + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showErrorDetailsDialog(error: Throwable) { + ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt new file mode 100644 index 00000000..d3410ea8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.modules.settings.appearance + +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import timber.log.Timber +import javax.inject.Inject + +class AppearancePresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, + private val analytics: AnalyticsHelper, + private val appInfo: AppInfo +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: AppearanceView) { + super.onAttachView(view) + Timber.i("Settings appearance view was initialized") + } + + fun onSharedPreferenceChanged(key: String?) { + key ?: return + Timber.i("Change settings $key") + + preferencesRepository.apply { + when (key) { + appThemeKey -> view?.recreateView() + appLanguageKey -> view?.run { + if (appLanguage == "system") { + updateLanguageToFollowSystem() + analytics.logEvent("language", "setting_changed" to appInfo.systemLanguage) + } else { + updateLanguage(appLanguage) + analytics.logEvent("language", "setting_changed" to appLanguage) + } + recreateView() + } + } + } + analytics.logEvent("setting_changed", "name" to key) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceView.kt new file mode 100644 index 00000000..ecee4f42 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceView.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.settings.appearance + +import io.github.wulkanowy.ui.base.BaseView + +interface AppearanceView : BaseView { + + fun recreateView() + + fun updateLanguage(langCode: String) + + fun updateLanguageToFollowSystem() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt new file mode 100644 index 00000000..f522913a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt @@ -0,0 +1,206 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.Destination +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +sealed class AppMenuItem { + + companion object { + val defaultAppMenuItemList = setOf( + DashboardAppMenuItem(), + GradeAppMenuItem(), + TimetableAppMenuItem(), + AttendanceAppMenuItem(), + ExamsAppMenuItem(), + HomeworkAppMenuItem(), + NoteAppMenuItem(), + LuckyNumberAppMenuItem(), + SchoolAnnouncementsAppMenuItem(), + SchoolAndTeachersAppMenuItem(), + MobileDevicesAppMenuItem(), + ConferenceAppMenuItem(), + MessageAppMenuItem() + ).sortedBy { it.order } + } + + // https://youtrack.jetbrains.com/issue/KT-38958 + abstract var order: Int + + abstract val icon: Int + + abstract val title: Int + + abstract val destinationType: Destination.Type + + @Serializable + data class DashboardAppMenuItem(override var order: Int = 0) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_dashboard + + @Transient + override val title = R.string.dashboard_title + + @Transient + override val destinationType = Destination.Type.DASHBOARD + } + + @Serializable + data class GradeAppMenuItem(override var order: Int = 1) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_grade + + @Transient + override val title = R.string.grade_title + + @Transient + override val destinationType = Destination.Type.GRADE + } + + @Serializable + data class AttendanceAppMenuItem(override var order: Int = 2) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_attendance + + @Transient + override val title = R.string.attendance_title + + @Transient + override val destinationType = Destination.Type.ATTENDANCE + } + + @Serializable + data class TimetableAppMenuItem(override var order: Int = 3) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_timetable + + @Transient + override val title = R.string.timetable_title + + @Transient + override val destinationType = Destination.Type.TIMETABLE + } + + @Serializable + data class MessageAppMenuItem(override var order: Int = 4) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_messages + + @Transient + override val title = R.string.message_title + + @Transient + override val destinationType = Destination.Type.MESSAGE + } + + @Serializable + data class ExamsAppMenuItem(override var order: Int = 5) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_exam + + @Transient + override val title = R.string.exam_title + + @Transient + override val destinationType = Destination.Type.EXAM + } + + @Serializable + data class HomeworkAppMenuItem(override var order: Int = 6) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_homework + + @Transient + override val title = R.string.homework_title + + @Transient + override val destinationType = Destination.Type.HOMEWORK + } + + @Serializable + data class NoteAppMenuItem(override var order: Int = 7) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_note + + @Transient + override val title = R.string.note_title + + @Transient + override val destinationType = Destination.Type.NOTE + } + + @Serializable + data class LuckyNumberAppMenuItem(override var order: Int = 8) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_lucky_number + + @Transient + override val title = R.string.lucky_number_title + + @Transient + override val destinationType = Destination.Type.LUCKY_NUMBER + } + + @Serializable + data class ConferenceAppMenuItem(override var order: Int = 9) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_conferences + + @Transient + override val title = R.string.conferences_title + + @Transient + override val destinationType = Destination.Type.CONFERENCE + } + + @Serializable + data class SchoolAnnouncementsAppMenuItem(override var order: Int = 10) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_all_about + + @Transient + override val title = R.string.school_announcement_title + + @Transient + override val destinationType = Destination.Type.SCHOOL_ANNOUNCEMENT + } + + @Serializable + data class SchoolAndTeachersAppMenuItem(override var order: Int = 11) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_schoolandteachers + + @Transient + override val title = R.string.schoolandteachers_title + + @Transient + override val destinationType = Destination.Type.SCHOOL_AND_TEACHERS + } + + @Serializable + data class MobileDevicesAppMenuItem(override var order: Int = 12) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_mobile_devices + + @Transient + override val title = R.string.mobile_devices_title + + @Transient + override val destinationType = Destination.Type.MOBILE_DEVICE + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt new file mode 100644 index 00000000..49196fad --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import java.util.* + +class MenuItemMoveCallback( + private val menuOrderAdapter: MenuOrderAdapter, + private var onUserInteractionEndListener: (List) -> Unit = {} +) : ItemTouchHelper.Callback() { + + override fun isLongPressDragEnabled() = true + + override fun isItemViewSwipeEnabled() = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + //Not implemented + } + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val list = menuOrderAdapter.items.toMutableList() + + Collections.swap(list, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + + menuOrderAdapter.submitList(list) + return true + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + onUserInteractionEndListener(menuOrderAdapter.items.toList()) + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt new file mode 100644 index 00000000..6bdd2fe0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemMenuOrderBinding +import javax.inject.Inject + +class MenuOrderAdapter @Inject constructor() : + RecyclerView.Adapter() { + + val items = mutableListOf() + + fun submitList(newItems: List) { + val diffResult = DiffUtil.calculateDiff(DiffCallback(newItems, items.toMutableList())) + + with(items) { + clear() + addAll(newItems) + } + + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemMenuOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position].appMenuItem + + with(holder.binding) { + menuOrderItemTitle.setText(item.title) + menuOrderItemIcon.setImageResource(item.icon) + } + } + + class ViewHolder(val binding: ItemMenuOrderBinding) : RecyclerView.ViewHolder(binding.root) + + private class DiffCallback( + private val oldList: List, + private val newList: List + ) : DiffUtil.Callback() { + + override fun getNewListSize() = newList.size + + override fun getOldListSize() = oldList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].appMenuItem.destinationType == newList[newItemPosition].appMenuItem.destinationType + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt new file mode 100644 index 00000000..21a7f5e8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.ShapeDrawable +import android.view.View +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.forEach +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.utils.getThemeAttrColor + +class MenuOrderDividerItemDecoration(private val context: Context) : + DividerItemDecoration(context, VERTICAL) { + + private val dividerDrawable = ShapeDrawable() + .apply { + DrawableCompat.setTint(this, context.getThemeAttrColor(R.attr.colorDivider)) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + canvas.save() + val dividerLeft = parent.paddingLeft + val dividerRight = parent.width - parent.paddingRight + + parent.forEach { + if (parent.getChildAdapterPosition(it) == 3) { + val params = it.layoutParams as RecyclerView.LayoutParams + val dividerTop = it.bottom + params.bottomMargin + val dividerBottom = dividerTop + dividerDrawable.intrinsicHeight + + dividerDrawable.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom + 30) + dividerDrawable.draw(canvas) + } + } + + canvas.restore() + } + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, + state: RecyclerView.State + ) { + if (parent.getChildAdapterPosition(view) == 3) { + outRect.bottom = dividerDrawable.intrinsicHeight + 30 + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt new file mode 100644 index 00000000..e08fc5dd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt @@ -0,0 +1,101 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.activity.addCallback +import androidx.core.view.MenuProvider +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.SimpleItemAnimator +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentMenuOrderBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import javax.inject.Inject + + +@AndroidEntryPoint +class MenuOrderFragment : BaseFragment(R.layout.fragment_menu_order), + MenuOrderView, MainView.TitledView { + + @Inject + lateinit var presenter: MenuOrderPresenter + + @Inject + lateinit var menuOrderAdapter: MenuOrderAdapter + + override val titleStringId = R.string.menu_order_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMenuOrderBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + val itemTouchHelper = ItemTouchHelper( + MenuItemMoveCallback(menuOrderAdapter, presenter::onDragAndDropEnd) + ) + + itemTouchHelper.attachToRecyclerView(binding.menuOrderRecycler) + + with(binding.menuOrderRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = menuOrderAdapter + addItemDecoration(MenuOrderDividerItemDecoration(context)) + addItemDecoration(DividerItemDecoration(context)) + (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false + } + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + presenter.onBackSelected() + } + + initializeToolbar() + } + + private fun initializeToolbar() { + requireActivity().addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == android.R.id.home) { + presenter.onBackSelected() + return true + } + return false + } + + }, viewLifecycleOwner) + } + + override fun updateData(data: List) { + menuOrderAdapter.submitList(data) + } + + override fun restartApp() { + startActivity(MainActivity.getStartIntent(requireContext())) + requireActivity().finishAffinity() + } + + override fun popView() { + (activity as? MainActivity?)?.popView() + } + + override fun showRestartConfirmationDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.menu_order_confirm_title) + .setMessage(R.string.menu_order_confirm_content) + .setPositiveButton(R.string.menu_order_confirm_restart) { _, _ -> presenter.onConfirmRestart() } + .setNegativeButton(R.string.all_cancel) { _, _ -> presenter.onCancelRestart() } + .show() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt new file mode 100644 index 00000000..b82bc1bd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt @@ -0,0 +1,6 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +data class MenuOrderItem( + val appMenuItem: AppMenuItem, + val order: Int +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt new file mode 100644 index 00000000..1c90cc5e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class MenuOrderPresenter @Inject constructor( + studentRepository: StudentRepository, + errorHandler: ErrorHandler, + private val preferencesRepository: PreferencesRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var updatedMenuOrderItems = emptyList() + + override fun onAttachView(view: MenuOrderView) { + super.onAttachView(view) + view.initView() + Timber.i("Menu order view was initialized") + loadData() + } + + private fun loadData() { + val savedMenuItemList = (preferencesRepository.appMenuItemOrder) + .sortedBy { it.order } + .map { MenuOrderItem(it, it.order) } + + view?.updateData(savedMenuItemList) + } + + fun onDragAndDropEnd(list: List) { + val updatedList = list.mapIndexed { index, menuOrderItem -> + menuOrderItem.copy(order = index) + } + + updatedMenuOrderItems = updatedList + view?.updateData(updatedList) + } + + fun onBackSelected() { + if (updatedMenuOrderItems.isNotEmpty()) { + view?.showRestartConfirmationDialog() + } else { + view?.popView() + } + } + + fun onConfirmRestart() { + updatedMenuOrderItems.forEach { + it.appMenuItem.apply { + order = it.order + } + } + + preferencesRepository.appMenuItemOrder = updatedMenuOrderItems.map { it.appMenuItem } + view?.restartApp() + } + + fun onCancelRestart() { + view?.popView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt new file mode 100644 index 00000000..264e68cc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import io.github.wulkanowy.ui.base.BaseView + +interface MenuOrderView : BaseView { + + fun initView() + + fun updateData(data: List) + + fun restartApp() + + fun showRestartConfirmationDialog() + + fun popView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsFragment.kt new file mode 100644 index 00000000..e45a5443 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsFragment.kt @@ -0,0 +1,147 @@ +package io.github.wulkanowy.ui.modules.settings.mod_settings + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.preference.EditTextPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.AppInfo +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class ModSettingsFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener, + MainView.TitledView, ModSettingsView { + + @Inject + lateinit var presenter: ModSettingsPresenter + + @Inject + lateinit var appInfo: AppInfo + + @Inject + lateinit var preferencesRepository: PreferencesRepository + + override val titleStringId get() = R.string.pref_mod_settings_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences_mod_settings, rootKey) + + val attendancePercentagePreference: EditTextPreference? = findPreference("attendance_percentage") + attendancePercentagePreference?.setOnBindEditTextListener { editText -> + editText.inputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL + editText.filters = arrayOf( + android.text.InputFilter { source, _, _, dest, _, _ -> + if (source == "." && dest.isEmpty()) { + return@InputFilter "0." + } + + val input = dest.toString() + source.toString() + if (input == "100.00") { + return@InputFilter null + } + + val inputVal = input.toFloatOrNull() + if (inputVal != null && inputVal >= 0 && inputVal <= 100) { + null + } else { + "" + } + } + ) + } + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + presenter.onSharedPreferenceChanged(key) + } + + override fun showError(text: String, error: Throwable) { + (activity as? BaseActivity<*, *>)?.showError(text, error) + } + + override fun showMessage(text: String) { + (activity as? BaseActivity<*, *>)?.showMessage(text) + } + + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun openClearLoginView() { + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showErrorDetailsDialog(error: Throwable) { + ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + } + + override fun showAttendanceSettings(items: List) { + val entries = requireContext().resources.getStringArray(R.array.mod_settings_attendance_entries) + val values = requireContext().resources.getStringArray(R.array.mod_settings_attendance_values) + val selectedItemsState = values.map { value -> items.any { it.name == value } } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.attendance_title) + .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } + .setPositiveButton(android.R.string.ok) { dialog, _ -> + val selectedState = (dialog as AlertDialog).listView.checkedItemPositions + val selectedValues = values + .filterIndexed { index, _ -> selectedState[index] } + .map { DashboardItem.HiddenAttendanceTile.valueOf(it) } + + Timber.i("Selected attendance to hide: $selectedValues") + presenter.onAttendanceSettingsSelected(selectedValues) + } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + + override fun restartApp() { + startActivity(MainActivity.getStartIntent(requireContext())) + requireActivity().finishAffinity() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsPresenter.kt new file mode 100644 index 00000000..10bbefbc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsPresenter.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.modules.settings.mod_settings + +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.utils.AnalyticsHelper +import timber.log.Timber +import javax.inject.Inject + +class ModSettingsPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val analytics: AnalyticsHelper, + private val preferencesRepository: PreferencesRepository +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: ModSettingsView) { + super.onAttachView(view) + + Timber.i("Mod settings view was initialized") + } + + fun onSharedPreferenceChanged(key: String?) { + key ?: return + Timber.i("Change mod settings $key") + analytics.logEvent("setting_changed", "name" to key) + } + + fun onHiddenGradesSelected(selectedItems: List) { + preferencesRepository.hiddenGrades = selectedItems + view?.restartApp() + } + + fun onAttendanceSettingsSelected(selectedValues: List) { + preferencesRepository.hiddenAttendanceItems = selectedValues + view?.restartApp() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsView.kt new file mode 100644 index 00000000..0133d2a4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/mod_settings/ModSettingsView.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.settings.mod_settings + +import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem + +interface ModSettingsView : BaseView { + fun restartApp() + fun showAttendanceSettings(items: List) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt new file mode 100644 index 00000000..0bf9ddad --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -0,0 +1,257 @@ +package io.github.wulkanowy.ui.modules.settings.notifications + +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.thelittlefireman.appkillermanager.AppKillerManager +import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.openNotificationSettings +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationsFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener, + MainView.TitledView, NotificationsView { + + @Inject + lateinit var presenter: NotificationsPresenter + + @Inject + lateinit var appInfo: AppInfo + + override val titleStringId get() = R.string.pref_settings_notifications_title + + private val notificationsPermission = "android.permission.POST_NOTIFICATIONS" + + override val isNotificationPermissionGranted: Boolean + get() = ContextCompat.checkSelfPermission( + requireContext(), notificationsPermission + ) == PackageManager.PERMISSION_GRANTED + + override val isNotificationPiggybackPermissionGranted: Boolean + get() { + val packageNameList = + NotificationManagerCompat.getEnabledListenerPackages(requireContext()) + val appPackageName = requireContext().packageName + + return appPackageName in packageNameList + } + + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + presenter.onNotificationsPermissionResult() + } else openNotificationsPermissionDialog() + } + + private val notificationSettingsPiggybackContract = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + presenter.onNotificationPiggybackPermissionResult() + } + + private val notificationSettingsExactAlarmsContract = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + presenter.onNotificationExactAlarmPermissionResult() + } + + override fun initView(showDebugNotificationSwitch: Boolean) { + findPreference(getString(R.string.pref_key_notification_debug))?.isVisible = + showDebugNotificationSwitch + + findPreference(getString(R.string.pref_key_notifications_fix_issues))?.run { + isVisible = AppKillerManager.isDeviceSupported() + && AppKillerManager.isAnyActionAvailable(requireContext()) + + setOnPreferenceClickListener { + presenter.onFixSyncIssuesClicked() + true + } + } + + findPreference(getString(R.string.pref_key_notifications_system_settings)) + ?.setOnPreferenceClickListener { + presenter.onOpenSystemSettingsClicked() + true + } + } + + override fun onCreateRecyclerView( + inflater: LayoutInflater, + parent: ViewGroup, + state: Bundle? + ): RecyclerView = super.onCreateRecyclerView(inflater, parent, state) + .also { + it.itemAnimator = null + it.layoutAnimation = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences_notifications, rootKey) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + presenter.onSharedPreferenceChanged(key) + } + + override fun enableNotification(notificationKey: String, enable: Boolean) { + findPreference(notificationKey)?.run { + isEnabled = enable + summary = if (enable) null else getString(R.string.pref_notify_disabled_summary) + } + } + + override fun showError(text: String, error: Throwable) { + (activity as? BaseActivity<*, *>)?.showError(text, error) + } + + override fun showMessage(text: String) { + (activity as? BaseActivity<*, *>)?.showMessage(text) + } + + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun openClearLoginView() { + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showErrorDetailsDialog(error: Throwable) { + ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() + } + + override fun showFixSyncDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.pref_notify_fix_sync_issues) + .setMessage(R.string.pref_notify_fix_sync_issues_message) + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .setPositiveButton(R.string.pref_notify_open_system_settings) { _, _ -> + try { + AppKillerManager.doActionPowerSaving(requireContext()) + AppKillerManager.doActionAutoStart(requireContext()) + AppKillerManager.doActionNotification(requireContext()) + } catch (e: NoActionFoundException) { + requireContext().openInternetBrowser( + "https://dontkillmyapp.com/${AppKillerManager.getDevice()?.manufacturer}", + ::showMessage + ) + } + } + .show() + } + + override fun openSystemSettings() { + requireActivity().openNotificationSettings() + } + + override fun requestNotificationPermissions() { + requestPermissionLauncher.launch(notificationsPermission) + } + + override fun openNotificationsPermissionDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.notifications_header_title) + .setMessage(R.string.notifications_header_description) + .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> + requireActivity().openNotificationSettings() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + setNotificationPreferencesChecked(false) + } + .setOnDismissListener { setNotificationPreferencesChecked(false) } + .show() + } + + override fun openNotificationPiggyBackPermissionDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.pref_notification_piggyback_popup_title)) + .setMessage(getString(R.string.pref_notification_piggyback_popup_description)) + .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> + notificationSettingsPiggybackContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")) + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + setNotificationPiggybackPreferenceChecked(false) + } + .setOnDismissListener { setNotificationPiggybackPreferenceChecked(false) } + .show() + } + + override fun openNotificationExactAlarmSettings() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.pref_notification_exact_alarm_popup_title)) + .setMessage(getString(R.string.pref_notification_exact_alarm_popup_descriptions)) + .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> + notificationSettingsExactAlarmsContract.launch(Intent("android.settings.REQUEST_SCHEDULE_EXACT_ALARM")) + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + setUpcomingLessonsNotificationPreferenceChecked(false) + } + .setOnDismissListener { setUpcomingLessonsNotificationPreferenceChecked(false) } + .show() + } + + override fun setNotificationPreferencesChecked(isChecked: Boolean) { + findPreference(getString(R.string.pref_key_notifications_enable))?.isChecked = + isChecked + } + + override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) { + findPreference(getString(R.string.pref_key_notifications_piggyback))?.isChecked = + isChecked + } + + override fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean) { + findPreference(getString(R.string.pref_key_notifications_upcoming_lessons_enable))?.isChecked = + isChecked + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt new file mode 100644 index 00000000..b2938cf6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt @@ -0,0 +1,111 @@ +package io.github.wulkanowy.ui.modules.settings.notifications + +import com.chuckerteam.chucker.api.ChuckerCollector +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import timber.log.Timber +import javax.inject.Inject + +class NotificationsPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, + private val timetableNotificationHelper: TimetableNotificationSchedulerHelper, + private val appInfo: AppInfo, + private val analytics: AnalyticsHelper, + private val chuckerCollector: ChuckerCollector +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: NotificationsView) { + super.onAttachView(view) + + with(view) { + enableNotification( + notificationKey = preferencesRepository.notificationsEnableKey, + enable = preferencesRepository.isServiceEnabled + ) + initView(appInfo.isDebug) + } + + checkNotificationsPermissionState() + checkNotificationPiggybackState() + + Timber.i("Settings notifications view was initialized") + } + + fun onSharedPreferenceChanged(key: String?) { + key ?: return + Timber.i("Change settings $key") + + preferencesRepository.apply { + when (key) { + isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> { + if (!isUpcomingLessonsNotificationsEnable) { + timetableNotificationHelper.cancelNotification() + } else if (!timetableNotificationHelper.canScheduleExactAlarms()) { + view?.openNotificationExactAlarmSettings() + } + } + notificationsEnableKey -> { + if (isNotificationsEnable && view?.isNotificationPermissionGranted == false) { + view?.requestNotificationPermissions() + } + } + isDebugNotificationEnableKey -> { + chuckerCollector.showNotification = isDebugNotificationEnable + } + isNotificationPiggybackEnabledKey -> { + if (isNotificationPiggybackEnabled && view?.isNotificationPiggybackPermissionGranted == false) { + view?.openNotificationPiggyBackPermissionDialog() + } + } + } + } + analytics.logEvent("setting_changed", "name" to key) + } + + fun onFixSyncIssuesClicked() { + view?.showFixSyncDialog() + } + + fun onOpenSystemSettingsClicked() { + view?.openSystemSettings() + } + + fun onNotificationsPermissionResult() { + view?.run { + setNotificationPreferencesChecked(isNotificationPermissionGranted) + } + } + + fun onNotificationPiggybackPermissionResult() { + view?.run { + setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted) + } + } + + fun onNotificationExactAlarmPermissionResult() { + view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms()) + } + + private fun checkNotificationsPermissionState() { + if (preferencesRepository.isNotificationsEnable) { + view?.run { + setNotificationPreferencesChecked(isNotificationPermissionGranted) + } + } + } + + private fun checkNotificationPiggybackState() { + if (preferencesRepository.isNotificationPiggybackEnabled) { + view?.run { + setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt new file mode 100644 index 00000000..a391681c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.modules.settings.notifications + +import io.github.wulkanowy.ui.base.BaseView + +interface NotificationsView : BaseView { + + val isNotificationPermissionGranted: Boolean + + val isNotificationPiggybackPermissionGranted: Boolean + + fun initView(showDebugNotificationSwitch: Boolean) + + fun showFixSyncDialog() + + fun openSystemSettings() + + fun enableNotification(notificationKey: String, enable: Boolean) + + fun requestNotificationPermissions() + + fun openNotificationsPermissionDialog() + + fun openNotificationPiggyBackPermissionDialog() + + fun openNotificationExactAlarmSettings() + + fun setNotificationPreferencesChecked(isChecked: Boolean) + + fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) + + fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt new file mode 100644 index 00000000..d5714483 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -0,0 +1,123 @@ +package io.github.wulkanowy.ui.modules.settings.sync + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.main.MainView +import javax.inject.Inject + +@AndroidEntryPoint +class SyncFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener, + MainView.TitledView, SyncView { + + @Inject + lateinit var presenter: SyncPresenter + + override val titleStringId get() = R.string.pref_settings_sync_title + + override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success) + + override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed) + + override fun initView() { + findPreference(getString(R.string.pref_key_services_force_sync))?.run { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + presenter.onSyncNowClicked() + true + } + } + } + + override fun setLastSyncDate(lastSyncDate: String) { + findPreference(getString(R.string.pref_key_services_force_sync))?.run { + summary = getString(R.string.pref_services_last_full_sync_date, lastSyncDate) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.scheme_preferences_sync, rootKey) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + presenter.onSharedPreferenceChanged(key) + } + + override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { + findPreference(serviceEnablesKey)?.run { + summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" + isEnabled = !isHolidays + } + } + + override fun setSyncInProgress(inProgress: Boolean) { + if (activity == null || !isAdded) return + + findPreference(getString(R.string.pref_key_services_force_sync))?.run { + isEnabled = !inProgress + summary = if (inProgress) getString(R.string.pref_services_sync_in_progress) else "" + } + } + + override fun showError(text: String, error: Throwable) { + (activity as? BaseActivity<*, *>)?.showError(text, error) + } + + override fun showMessage(text: String) { + Snackbar.make(requireView(), text, Snackbar.LENGTH_LONG) + .apply { + anchorView = requireActivity().findViewById(R.id.main_bottom_nav) + show() + } + } + + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() + } + + override fun showChangePasswordSnackbar(redirectUrl: String) { + (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) + } + + override fun openClearLoginView() { + (activity as? BaseActivity<*, *>)?.openClearLoginView() + } + + override fun showErrorDetailsDialog(error: Throwable) { + ErrorDialog.newInstance(error).show(childFragmentManager, "error_details") + } + + override fun showAuthDialog() { + (activity as? BaseActivity<*, *>)?.showAuthDialog() + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt new file mode 100644 index 00000000..594e097a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt @@ -0,0 +1,90 @@ +package io.github.wulkanowy.ui.modules.settings.sync + +import androidx.work.WorkInfo +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.SyncManager +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.time.LocalDate.now +import javax.inject.Inject + +class SyncPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, + private val analytics: AnalyticsHelper, + private val syncManager: SyncManager, +) : BasePresenter(errorHandler, studentRepository) { + + override fun onAttachView(view: SyncView) { + super.onAttachView(view) + Timber.i("Settings sync view was initialized") + view.setServicesSuspended(preferencesRepository.serviceEnableKey, now().isHolidays) + view.initView() + setSyncDateInView() + } + + fun onSharedPreferenceChanged(key: String?) { + key ?: return + Timber.i("Change settings $key") + + preferencesRepository.apply { + when (key) { + serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } + servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) + } + } + analytics.logEvent("setting_changed", "name" to key) + } + + fun onSyncNowClicked() { + view?.run { + syncManager.startOneTimeSyncWorker().onEach { workInfo -> + when (workInfo?.state) { + WorkInfo.State.ENQUEUED -> { + setSyncInProgress(true) + Timber.i("Setting sync now started") + analytics.logEvent("sync_now", "status" to "started") + } + + WorkInfo.State.SUCCEEDED -> { + showMessage(syncSuccessString) + analytics.logEvent("sync_now", "status" to "success") + } + + WorkInfo.State.FAILED -> { + showError( + syncFailedString, + Throwable( + message = workInfo.outputData.getString("error_message"), + cause = Throwable(workInfo.outputData.getString("error_stack")) + ) + ) + analytics.logEvent("sync_now", "status" to "failed") + } + + else -> Timber.d("Sync now state: ${workInfo?.state}") + } + if (workInfo?.state?.isFinished == true) { + setSyncInProgress(false) + setSyncDateInView() + } + }.catch { + Timber.e(it, "Sync now failed") + }.launch("sync") + } + } + + private fun setSyncDateInView() { + val lastSyncDate = preferencesRepository.lasSyncDate ?: return + + view?.setLastSyncDate(lastSyncDate.toFormattedString("dd.MM.yyyy HH:mm:ss")) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt new file mode 100644 index 00000000..6ffe156f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncView.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.ui.modules.settings.sync + +import io.github.wulkanowy.ui.base.BaseView + +interface SyncView : BaseView { + + val syncSuccessString: String + + val syncFailedString: String + + fun initView() + + fun setLastSyncDate(lastSyncDate: String) + + fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) + + fun setSyncInProgress(inProgress: Boolean) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index 3e0106d1..cfb62849 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -1,21 +1,56 @@ package io.github.wulkanowy.ui.modules.splash +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.viewbinding.ViewBinding +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.services.shortcuts.ShortcutsHelper import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.openInternetBrowser +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import javax.inject.Inject -class SplashActivity : BaseActivity(), SplashView { +@SuppressLint("CustomSplashScreen") +@AndroidEntryPoint +class SplashActivity : BaseActivity(), SplashView { @Inject override lateinit var presenter: SplashPresenter + @Inject + lateinit var shortcutsHelper: ShortcutsHelper + + companion object { + + private const val EXTRA_START_DESTINATION = "start_destination_json" + + private const val EXTRA_EXTERNAL_URL = "external_url" + + fun getStartIntent(context: Context, destination: Destination? = null) = + Intent(context, SplashActivity::class.java).apply { + destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) } + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - presenter.onAttachView(this) + installSplashScreen().setKeepOnScreenCondition { true } + shortcutsHelper.initializeShortcuts() + + val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL) + val startDestinationJson = intent?.getStringExtra(EXTRA_START_DESTINATION) + + presenter.onAttachView(this, externalLink, startDestinationJson) } override fun openLoginView() { @@ -23,8 +58,13 @@ class SplashActivity : BaseActivity(), SplashView { finish() } - override fun openMainView() { - startActivity(MainActivity.getStartIntent(this)) + override fun openMainView(destination: Destination?) { + startActivity(MainActivity.getStartIntent(this, destination)) + finish() + } + + override fun openExternalUrlAndFinish(url: String) { + openInternetBrowser(url, ::showMessage) finish() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt index 44b53c5f..767c885c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt @@ -1,27 +1,40 @@ package io.github.wulkanowy.ui.modules.splash -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.Destination +import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import javax.inject.Inject class SplashPresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, - studentRepository: StudentRepository -) : BasePresenter(errorHandler, studentRepository, schedulers) { + studentRepository: StudentRepository, + private val json: Json +) : BasePresenter(errorHandler, studentRepository) { - override fun onAttachView(view: SplashView) { + fun onAttachView(view: SplashView, externalUrl: String?, startDestinationJson: String?) { super.onAttachView(view) - disposable.add(studentRepository.isCurrentStudentSet() - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - view.apply { - if (it) openMainView() - else openLoginView() + + val startDestination: Destination? = startDestinationJson?.let { json.decodeFromString(it) } + + if (!externalUrl.isNullOrBlank()) { + view.openExternalUrlAndFinish(externalUrl) + return + } + + presenterScope.launch { + runCatching { studentRepository.isCurrentStudentSet() } + .onFailure(errorHandler::dispatch) + .onSuccess { + if (it) { + view.openMainView(startDestination) + } else { + view.openLoginView() + } } - }, { errorHandler.dispatch(it) })) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt index 9efd8123..1c5d8bfd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.ui.modules.splash import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.Destination interface SplashView : BaseView { fun openLoginView() - fun openMainView() + fun openMainView(destination: Destination?) + + fun openExternalUrlAndFinish(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt new file mode 100644 index 00000000..60912200 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt @@ -0,0 +1,45 @@ +package io.github.wulkanowy.ui.modules.studentinfo + +import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemStudentInfoBinding +import javax.inject.Inject + +class StudentInfoAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = listOf() + + var onItemClickListener: (StudentInfoView.Type?) -> Unit = {} + + var onItemLongClickListener: (text: String) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemStudentInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + studentInfoItemTitle.text = item.title + studentInfoItemSubtitle.text = item.subtitle + studentInfoItemArrow.visibility = if (item.showArrow) VISIBLE else GONE + + with(root) { + setOnClickListener { onItemClickListener(item.viewType) } + setOnLongClickListener { + onItemLongClickListener(studentInfoItemSubtitle.text.toString()) + true + } + } + } + } + + class ViewHolder(val binding: ItemStudentInfoBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt new file mode 100644 index 00000000..598046a2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt @@ -0,0 +1,264 @@ +package io.github.wulkanowy.ui.modules.studentinfo + +import android.content.ClipData +import android.content.ClipboardManager +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import android.widget.Toast +import androidx.core.content.getSystemService +import androidx.core.os.bundleOf +import androidx.core.view.get +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.StudentGuardian +import io.github.wulkanowy.data.db.entities.StudentInfo +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.enums.Gender +import io.github.wulkanowy.databinding.FragmentStudentInfoBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nullableSerializable +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class StudentInfoFragment : + BaseFragment(R.layout.fragment_student_info), StudentInfoView, + MainView.TitledView { + + @Inject + lateinit var presenter: StudentInfoPresenter + + @Inject + lateinit var studentInfoAdapter: StudentInfoAdapter + + override val titleStringId: Int + get() = when ( + requireArguments().nullableSerializable(INFO_TYPE_ARGUMENT_KEY) + ) { + StudentInfoView.Type.PERSONAL -> R.string.account_personal_data + StudentInfoView.Type.CONTACT -> R.string.account_contact + StudentInfoView.Type.ADDRESS -> R.string.account_address + StudentInfoView.Type.FAMILY -> R.string.account_family + StudentInfoView.Type.SECOND_GUARDIAN -> R.string.student_info_guardian + StudentInfoView.Type.FIRST_GUARDIAN -> R.string.student_info_guardian + else -> R.string.student_info_title + } + + override val isViewEmpty get() = studentInfoAdapter.items.isEmpty() + + companion object { + + private const val INFO_TYPE_ARGUMENT_KEY = "info_type" + + private const val STUDENT_ARGUMENT_KEY = "student_with_semesters" + + fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) = + StudentInfoFragment().apply { + arguments = bundleOf( + INFO_TYPE_ARGUMENT_KEY to type, + STUDENT_ARGUMENT_KEY to studentWithSemesters + ) + } + } + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentStudentInfoBinding.bind(view) + presenter.onAttachView( + view = this, + type = requireArguments().serializable(INFO_TYPE_ARGUMENT_KEY), + studentWithSemesters = requireArguments().serializable(STUDENT_ARGUMENT_KEY), + ) + } + + override fun initView() { + with(binding) { + studentInfoSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + studentInfoSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + studentInfoSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) + ) + studentInfoErrorRetry.setOnClickListener { presenter.onRetry() } + studentInfoErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + + with(studentInfoAdapter) { + onItemClickListener = presenter::onItemSelected + onItemLongClickListener = presenter::onItemLongClick + } + + with(binding.studentInfoRecycler) { + layoutManager = LinearLayoutManager(context) + addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL)) + setHasFixedSize(true) + adapter = studentInfoAdapter + } + } + + override fun updateData(data: List) { + with(studentInfoAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + menu[0].isVisible = false + } + + override fun showPersonalTypeData(studentInfo: StudentInfo) { + updateData( + listOf( + getString(R.string.student_info_first_name) to studentInfo.firstName, + getString(R.string.student_info_second_name) to studentInfo.secondName, + getString(R.string.student_info_last_name) to studentInfo.surname, + getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female), + getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no), + getString(R.string.student_info_family_name) to studentInfo.familyName, + getString(R.string.student_info_parents_name) to studentInfo.parentsNames + ).map { + StudentInfoItem( + title = it.first, + subtitle = it.second.ifBlank { getString(R.string.all_no_data) }, + showArrow = false, + ) + } + ) + } + + override fun showContactTypeData(studentInfo: StudentInfo) { + updateData( + listOf( + getString(R.string.student_info_phone) to studentInfo.phoneNumber, + getString(R.string.student_info_cellphone) to studentInfo.cellPhoneNumber, + getString(R.string.student_info_email) to studentInfo.email + ).map { + StudentInfoItem( + title = it.first, + subtitle = it.second.ifBlank { getString(R.string.all_no_data) }, + showArrow = false, + ) + } + ) + } + + override fun showFamilyTypeData(studentInfo: StudentInfo) { + val items = buildList { + add(studentInfo.firstGuardian?.let { + Triple(it.kinship.capitalise(), it.fullName, StudentInfoView.Type.FIRST_GUARDIAN) + }) + + add(studentInfo.secondGuardian?.let { + Triple(it.kinship.capitalise(), it.fullName, StudentInfoView.Type.SECOND_GUARDIAN) + }) + }.filterNotNull() + + updateData( + items.map { (title, value, type) -> + StudentInfoItem( + title = title.ifBlank { getString(R.string.all_no_data) }, + subtitle = value.ifBlank { getString(R.string.all_no_data) }, + showArrow = true, + viewType = type, + ) + } + ) + } + + override fun showAddressTypeData(studentInfo: StudentInfo) { + updateData( + listOf( + getString(R.string.student_info_address) to studentInfo.address, + getString(R.string.student_info_registered_address) to studentInfo.registeredAddress, + getString(R.string.student_info_correspondence_address) to studentInfo.correspondenceAddress + ).map { + StudentInfoItem( + title = it.first, + subtitle = it.second.ifBlank { getString(R.string.all_no_data) }, + showArrow = false, + ) + } + ) + } + + override fun showGuardianTypeData(studentGuardian: StudentGuardian) { + updateData( + listOf( + getString(R.string.student_info_full_name) to studentGuardian.fullName, + getString(R.string.student_info_kinship) to studentGuardian.kinship, + getString(R.string.student_info_guardian_address) to studentGuardian.address, + getString(R.string.student_info_phones) to studentGuardian.phones, + getString(R.string.student_info_email) to studentGuardian.email + ).map { + StudentInfoItem( + title = it.first, + subtitle = it.second.ifBlank { getString(R.string.all_no_data) }, + showArrow = false, + ) + } + ) + } + + override fun openStudentInfoView( + infoType: StudentInfoView.Type, + studentWithSemesters: StudentWithSemesters + ) { + (requireActivity() as MainActivity).pushView(newInstance(infoType, studentWithSemesters)) + } + + override fun showEmpty(show: Boolean) { + binding.studentInfoEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.studentInfoError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.studentInfoErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.studentInfoProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun enableSwipe(enable: Boolean) { + binding.studentInfoSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.studentInfoRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun hideRefresh() { + binding.studentInfoSwipe.isRefreshing = false + } + + override fun copyToClipboard(text: String) { + val clipData = ClipData.newPlainText("student_info_wulkanowy", text) + requireActivity().getSystemService()?.setPrimaryClip(clipData) + Toast.makeText(context, R.string.all_copied, Toast.LENGTH_SHORT).show() + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoItem.kt new file mode 100644 index 00000000..21226539 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoItem.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.ui.modules.studentinfo + +data class StudentInfoItem( + val title: String, + val subtitle: String, + val showArrow: Boolean, + val viewType: StudentInfoView.Type? = null, +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt new file mode 100644 index 00000000..083b590b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt @@ -0,0 +1,141 @@ +package io.github.wulkanowy.ui.modules.studentinfo + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.StudentInfo +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.repositories.StudentInfoRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.getCurrentOrLast +import timber.log.Timber +import javax.inject.Inject + +class StudentInfoPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val studentInfoRepository: StudentInfoRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var infoType: StudentInfoView.Type + + private lateinit var studentWithSemesters: StudentWithSemesters + + private lateinit var lastError: Throwable + + fun onAttachView( + view: StudentInfoView, + type: StudentInfoView.Type, + studentWithSemesters: StudentWithSemesters + ) { + super.onAttachView(view) + infoType = type + this.studentWithSemesters = studentWithSemesters + view.initView() + Timber.i("Student info $infoType view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + loadData(true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(true) + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + fun onItemSelected(viewType: StudentInfoView.Type?) { + viewType ?: return + + view?.openStudentInfoView( + studentWithSemesters = studentWithSemesters, + infoType = viewType, + ) + } + + fun onItemLongClick(text: String) { + view?.copyToClipboard(text) + } + + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val semester = studentWithSemesters.semesters.getCurrentOrLast() + studentInfoRepository.getStudentInfo( + student = studentWithSemesters.student, + semester = semester, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("load student info $infoType") + .onResourceData { + val isFamily = infoType == StudentInfoView.Type.FAMILY + val isFirstGuardianEmpty = it?.firstGuardian == null + val isSecondGuardianEmpty = it?.secondGuardian == null + if (it != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) { + Timber.i("Loading student info $infoType result: Success") + showCorrectData(it) + view?.run { + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else { + Timber.i("Loading student info $infoType result: No student or family info found") + view?.run { + showContent(!isViewEmpty) + showEmpty(isViewEmpty) + showErrorView(false) + } + } + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "student_info") + } + } + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun showCorrectData(studentInfo: StudentInfo) { + when (infoType) { + StudentInfoView.Type.PERSONAL -> view?.showPersonalTypeData(studentInfo) + StudentInfoView.Type.CONTACT -> view?.showContactTypeData(studentInfo) + StudentInfoView.Type.ADDRESS -> view?.showAddressTypeData(studentInfo) + StudentInfoView.Type.FAMILY -> view?.showFamilyTypeData(studentInfo) + StudentInfoView.Type.SECOND_GUARDIAN -> view?.showGuardianTypeData(studentInfo.secondGuardian!!) + StudentInfoView.Type.FIRST_GUARDIAN -> view?.showGuardianTypeData(studentInfo.firstGuardian!!) + } + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + showContent(false) + showProgress(false) + } else showError(message, error) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt new file mode 100644 index 00000000..c20359df --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt @@ -0,0 +1,47 @@ +package io.github.wulkanowy.ui.modules.studentinfo + +import io.github.wulkanowy.data.db.entities.StudentGuardian +import io.github.wulkanowy.data.db.entities.StudentInfo +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.ui.base.BaseView + +interface StudentInfoView : BaseView { + + enum class Type { + PERSONAL, ADDRESS, CONTACT, FAMILY, FIRST_GUARDIAN, SECOND_GUARDIAN + } + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun showPersonalTypeData(studentInfo: StudentInfo) + + fun showContactTypeData(studentInfo: StudentInfo) + + fun showAddressTypeData(studentInfo: StudentInfo) + + fun showFamilyTypeData(studentInfo: StudentInfo) + + fun showGuardianTypeData(studentGuardian: StudentGuardian) + + fun openStudentInfoView(infoType: Type, studentWithSemesters: StudentWithSemesters) + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) + + fun hideRefresh() + + fun copyToClipboard(text: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt new file mode 100644 index 00000000..5cb6c401 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -0,0 +1,357 @@ +package io.github.wulkanowy.ui.modules.timetable + +import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.databinding.ItemTimetableBinding +import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding +import io.github.wulkanowy.databinding.ItemTimetableMainAdditionalBinding +import io.github.wulkanowy.databinding.ItemTimetableSmallBinding +import io.github.wulkanowy.utils.SyncListAdapter +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class TimetableAdapter @Inject constructor() : + SyncListAdapter(Differ) { + + override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + + return when (TimetableItemType.entries[viewType]) { + TimetableItemType.SMALL -> SmallViewHolder( + ItemTimetableSmallBinding.inflate(inflater, parent, false) + ) + + TimetableItemType.NORMAL -> NormalViewHolder( + ItemTimetableBinding.inflate(inflater, parent, false) + ) + + TimetableItemType.EMPTY -> EmptyViewHolder( + ItemTimetableEmptyBinding.inflate(inflater, parent, false) + ) + + TimetableItemType.ADDITIONAL -> AdditionalViewHolder( + ItemTimetableMainAdditionalBinding.inflate(inflater, parent, false) + ) + } + } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isNotEmpty() && holder is NormalViewHolder) { + updateTimeLeft( + binding = holder.binding, + timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft, + ) + } else super.onBindViewHolder(holder, position, payloads) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is SmallViewHolder -> bindSmallView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Small, + ) + is NormalViewHolder -> bindNormalView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Normal, + ) + is EmptyViewHolder -> bindEmptyView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Empty, + ) + + is AdditionalViewHolder -> bindAdditionalView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Additional, + ) + } + } + + private fun bindAdditionalView( + binding: ItemTimetableMainAdditionalBinding, + item: TimetableItem.Additional + ) { + with(binding) { + timetableItemSubject.text = item.additional.subject + timetableItemTimeStart.text = item.additional.start.toFormattedString("HH:mm") + timetableItemTimeFinish.text = item.additional.end.toFormattedString("HH:mm") + } + } + + private fun bindSmallView(binding: ItemTimetableSmallBinding, item: TimetableItem.Small) { + val lesson = item.lesson + + with(binding) { + timetableSmallItemNumber.text = lesson.number.toString() + timetableSmallItemNumber.isVisible = item.isLessonNumberVisible + timetableSmallItemSubject.text = lesson.subject + timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm") + timetableSmallItemRoom.text = lesson.room + timetableSmallItemTeacher.text = lesson.teacher + + bindSubjectStyle(timetableSmallItemSubject, lesson) + bindSmallDescription(binding, lesson) + bindSmallColors(binding, lesson) + + root.setOnClickListener { item.onClick(lesson) } + } + } + + private fun bindNormalView(binding: ItemTimetableBinding, item: TimetableItem.Normal) { + val lesson = item.lesson + + with(binding) { + timetableItemNumber.text = lesson.number.toString() + timetableItemNumber.isVisible = item.isLessonNumberVisible + timetableItemSubject.text = lesson.subject + timetableItemGroup.text = lesson.group + timetableItemRoom.text = lesson.room + timetableItemTeacher.text = lesson.teacher + timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm") + timetableItemTimeFinish.text = lesson.end.toFormattedString("HH:mm") + + bindSubjectStyle(timetableItemSubject, lesson) + bindNormalDescription(binding, item) + bindNormalColors(binding, lesson) + updateTimeLeft(binding, item.timeLeft) + + root.setOnClickListener { item.onClick(lesson) } + } + } + + private fun bindEmptyView(binding: ItemTimetableEmptyBinding, item: TimetableItem.Empty) { + with(binding) { + timetableEmptyItemNumber.text = when (item.numFrom) { + item.numTo -> item.numFrom.toString() + else -> "${item.numFrom}-${item.numTo}" + } + timetableEmptyItemSubject.text = timetableEmptyItemSubject.context.getPlural( + R.plurals.timetable_no_lesson, + item.numTo - item.numFrom + 1 + ) + } + } + + private fun updateTimeLeft(binding: ItemTimetableBinding, timeLeft: TimeLeft?) { + with(binding) { + when { + // before lesson + timeLeft?.until != null -> { + timetableItemTimeLeft.visibility = GONE + with(timetableItemTimeUntil) { + visibility = VISIBLE + text = context.getString( + R.string.timetable_time_until, + context.getString( + R.string.timetable_minutes, + timeLeft.until.toMinutes().toString(10) + ) + ) + } + } + // after lesson start + timeLeft?.left != null -> { + timetableItemTimeUntil.visibility = GONE + with(timetableItemTimeLeft) { + visibility = VISIBLE + text = context.getString( + R.string.timetable_time_left, + context.getString( + R.string.timetable_minutes, + timeLeft.left.toMinutes().toString() + ) + ) + } + } + // right after lesson finish + timeLeft?.isJustFinished == true -> { + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = VISIBLE + timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished) + } + + else -> { + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = GONE + } + } + } + } + + private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) { + subjectView.paint.isStrikeThruText = lesson.canceled + } + + private fun bindSmallDescription(binding: ItemTimetableSmallBinding, lesson: Timetable) { + with(binding) { + if (lesson.info.isNotBlank() && !lesson.changes) { + timetableSmallItemDescription.visibility = VISIBLE + timetableSmallItemDescription.text = lesson.info + + timetableSmallItemRoom.visibility = GONE + timetableSmallItemTeacher.visibility = GONE + + timetableSmallItemDescription.setTextColor( + root.context.getThemeAttrColor( + if (lesson.canceled) R.attr.colorTimetableCanceled + else R.attr.colorTimetableChange + ) + ) + } else { + timetableSmallItemDescription.visibility = GONE + timetableSmallItemRoom.visibility = VISIBLE + timetableSmallItemTeacher.visibility = VISIBLE + } + } + } + + private fun bindNormalDescription(binding: ItemTimetableBinding, item: TimetableItem.Normal) { + val lesson = item.lesson + with(binding) { + if (lesson.info.isNotBlank() && !lesson.changes) { + timetableItemDescription.visibility = VISIBLE + timetableItemDescription.text = lesson.info + + timetableItemRoom.visibility = GONE + timetableItemGroup.visibility = GONE + timetableItemTeacher.visibility = GONE + + timetableItemDescription.setTextColor( + root.context.getThemeAttrColor( + if (lesson.canceled) R.attr.colorTimetableCanceled + else R.attr.colorTimetableChange + ) + ) + } else { + timetableItemDescription.visibility = GONE + timetableItemRoom.isVisible = + lesson.room.isNotBlank() || lesson.roomOld.isNotBlank() + timetableItemGroup.isVisible = item.showGroupsInPlan && lesson.group.isNotBlank() + timetableItemTeacher.visibility = VISIBLE + } + } + } + + private fun bindSmallColors(binding: ItemTimetableSmallBinding, lesson: Timetable) { + with(binding) { + if (lesson.canceled) { + updateNumberAndSubjectCanceledColor( + timetableSmallItemNumber, + timetableSmallItemSubject + ) + } else { + updateNumberColor(timetableSmallItemNumber, lesson) + updateSubjectColor(timetableSmallItemSubject, lesson) + updateRoomColor(timetableSmallItemRoom, lesson) + updateTeacherColor(timetableSmallItemTeacher, lesson) + } + } + } + + private fun bindNormalColors(binding: ItemTimetableBinding, lesson: Timetable) { + with(binding) { + if (lesson.canceled) { + updateNumberAndSubjectCanceledColor(timetableItemNumber, timetableItemSubject) + } else { + updateNumberColor(timetableItemNumber, lesson) + updateSubjectColor(timetableItemSubject, lesson) + updateRoomColor(timetableItemRoom, lesson) + updateTeacherColor(timetableItemTeacher, lesson) + } + } + } + + private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { + numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) + subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) + } + + private fun updateNumberColor(numberView: TextView, lesson: Timetable) { + numberView.setTextColor( + numberView.context.getThemeAttrColor( + if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + ) + ) + } + + private fun updateSubjectColor(subjectView: TextView, lesson: Timetable) { + subjectView.setTextColor( + subjectView.context.getThemeAttrColor( + if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + ) + ) + } + + private fun updateRoomColor(roomView: TextView, lesson: Timetable) { + roomView.setTextColor( + roomView.context.getThemeAttrColor( + if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange + else android.R.attr.textColorSecondary + ) + ) + } + + private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) { + teacherTextView.setTextColor( + teacherTextView.context.getThemeAttrColor( + if (lesson.teacherOld.isNotBlank()) R.attr.colorTimetableChange + else android.R.attr.textColorSecondary + ) + ) + } + + private class NormalViewHolder(val binding: ItemTimetableBinding) : + RecyclerView.ViewHolder(binding.root) + + private class SmallViewHolder(val binding: ItemTimetableSmallBinding) : + RecyclerView.ViewHolder(binding.root) + + private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) : + RecyclerView.ViewHolder(binding.root) + + private class AdditionalViewHolder(val binding: ItemTimetableMainAdditionalBinding) : + RecyclerView.ViewHolder(binding.root) + + private object Differ : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean = + when { + oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> { + oldItem.lesson.start == newItem.lesson.start + } + + oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> { + oldItem.lesson.start == newItem.lesson.start + } + + else -> oldItem == newItem + } + + override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) = + oldItem == newItem + + override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? { + return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) { + if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) { + "time_left" + } else super.getChangePayload(oldItem, newItem) + } else super.getChangePayload(oldItem, newItem) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index 57ec5998..e8a85347 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -1,52 +1,52 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint +import android.app.Dialog import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.synthetic.main.dialog_timetable.* -import org.threeten.bp.LocalDateTime +import io.github.wulkanowy.databinding.DialogTimetableBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.* +import java.time.Instant -class TimetableDialog : DialogFragment() { +@AndroidEntryPoint +class TimetableDialog : BaseDialogFragment() { private lateinit var lesson: Timetable companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Timetable): TimetableDialog { - return TimetableDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } - } + fun newInstance(lesson: Timetable) = TimetableDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to lesson) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - lesson = getSerializable(ARGUMENT_KEY) as Timetable - } + lesson = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_timetable, container, false) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogTimetableBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - lesson.run { - setInfo(info, teacher, canceled, changes) + with(lesson) { + setInfo(info, canceled, changes) setSubject(subject, subjectOld) setTeacher(teacher, teacherOld) setGroup(group) @@ -54,107 +54,141 @@ class TimetableDialog : DialogFragment() { setTime(start, end) } - timetableDialogClose.setOnClickListener { dismiss() } + binding.timetableDialogClose.setOnClickListener { dismiss() } } private fun setSubject(subject: String, subjectOld: String) { - timetableDialogSubject.text = subject - if (subjectOld.isNotBlank() && subjectOld != subject) { - timetableDialogSubject.run { - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = subjectOld - } - timetableDialogSubjectNew.run { - visibility = VISIBLE - text = subject + with(binding) { + timetableDialogLessonValue.text = subject + if (subjectOld.isNotBlank() && subjectOld != subject) { + timetableDialogLessonValue.run { + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = subjectOld + } + timetableDialogLessonNewValue.run { + visibility = VISIBLE + text = subject + } } } } - private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) { - when { - info.isNotBlank() -> { - if (canceled) { - timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - } else { - timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) - timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange)) - } + @SuppressLint("DefaultLocale") + private fun setInfo(info: String, canceled: Boolean, changes: Boolean) { + with(binding) { + when { + info.isNotBlank() -> { + if (canceled) { + timetableDialogChangesTitle.setTextColor( + requireContext().getThemeAttrColor( + R.attr.colorTimetableCanceled + ) + ) + timetableDialogChangesValue.setTextColor( + requireContext().getThemeAttrColor( + R.attr.colorTimetableCanceled + ) + ) + } else { + timetableDialogChangesTitle.setTextColor( + requireContext().getThemeAttrColor( + R.attr.colorTimetableChange + ) + ) + timetableDialogChangesValue.setTextColor( + requireContext().getThemeAttrColor( + R.attr.colorTimetableChange + ) + ) + } - timetableDialogChanges.text = when { - canceled && !changes -> "Lekcja odwołana: $info" - changes && teacher.isNotBlank() -> "Zastępstwo: $teacher" - changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}" - else -> info.capitalize() + timetableDialogChangesValue.text = when { + canceled && !changes -> "Lekcja odwołana: $info" + else -> info.capitalise() + } + } + else -> { + timetableDialogChangesTitle.visibility = GONE + timetableDialogChangesValue.visibility = GONE } - } else -> { - timetableDialogChangesTitle.visibility = GONE - timetableDialogChanges.visibility = GONE } } } private fun setTeacher(teacher: String, teacherOld: String) { - when { - teacherOld.isNotBlank() && teacherOld != teacher -> { - timetableDialogTeacher.run { - visibility = VISIBLE - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = teacherOld + with(binding) { + when { + teacherOld.isNotBlank() && teacherOld != teacher -> { + timetableDialogTeacherValue.run { + visibility = VISIBLE + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = teacherOld + } + if (teacher.isNotBlank()) { + timetableDialogTeacherNewValue.run { + visibility = VISIBLE + text = teacher + } + } } - if (teacher.isNotBlank()) { - timetableDialogTeacherNew.run { + teacherOld.isNotBlank() && teacherOld == teacher -> { + timetableDialogTeacherValue.run { + visibility = GONE + } + timetableDialogTeacherNewValue.run { visibility = VISIBLE text = teacher } } - } - teacher.isNotBlank() -> timetableDialogTeacher.text = teacher - else -> { - timetableDialogTeacherTitle.visibility = GONE - timetableDialogTeacher.visibility = GONE + teacher.isNotBlank() -> timetableDialogTeacherValue.text = teacher + else -> { + timetableDialogTeacherTitle.visibility = GONE + timetableDialogTeacherValue.visibility = GONE + } } } } private fun setGroup(group: String) { - group.let { + with(binding) { when { - it.isNotBlank() -> timetableDialogGroup.text = it + group.isNotBlank() -> timetableDialogGroupValue.text = group else -> { timetableDialogGroupTitle.visibility = GONE - timetableDialogGroup.visibility = GONE + timetableDialogGroupValue.visibility = GONE } } } } private fun setRoom(room: String, roomOld: String) { - when { - roomOld.isNotBlank() && roomOld != room -> { - timetableDialogRoom.run { - visibility = VISIBLE - paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG - text = roomOld - } - if (room.isNotBlank()) { - timetableDialogRoomNew.run { + with(binding) { + when { + roomOld.isNotBlank() && roomOld != room -> { + timetableDialogRoomValue.run { visibility = VISIBLE - text = room + paintFlags = paintFlags or STRIKE_THRU_TEXT_FLAG + text = roomOld + } + if (room.isNotBlank()) { + timetableDialogRoomNewValue.run { + visibility = VISIBLE + text = room + } } } - } - room.isNotBlank() -> timetableDialogRoom.text = room - else -> { - timetableDialogRoomTitle.visibility = GONE - timetableDialogRoom.visibility = GONE + room.isNotBlank() -> timetableDialogRoomValue.text = room + else -> { + timetableDialogRoomTitle.visibility = GONE + timetableDialogRoomValue.visibility = GONE + } } } } @SuppressLint("SetTextI18n") - private fun setTime(start: LocalDateTime, end: LocalDateTime) { - timetableDialogTime.text = "${start.toFormattedString("HH:mm")} - ${end.toFormattedString("HH:mm")}" + private fun setTime(start: Instant, end: Instant) { + binding.timetableDialogTimeValue.text = + "${start.toFormattedString("HH:mm")} - ${end.toFormattedString("HH:mm")}" } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index ef6057df..b73e7c26 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -1,89 +1,99 @@ package io.github.wulkanowy.ui.modules.timetable import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import androidx.core.os.bundleOf +import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.databinding.FragmentTimetableBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment -import io.github.wulkanowy.utils.SchooldaysRangeLimiter +import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_timetable.* -import org.threeten.bp.LocalDate +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker +import java.time.LocalDate import javax.inject.Inject -class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, - MainView.TitledView { +@AndroidEntryPoint +class TimetableFragment : BaseFragment(R.layout.fragment_timetable), + TimetableView, MainView.MainChildView, MainView.TitledView { @Inject lateinit var presenter: TimetablePresenter @Inject - lateinit var timetableAdapter: FlexibleAdapter> + lateinit var timetableAdapter: TimetableAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" - fun newInstance() = TimetableFragment() + private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE" + + fun newInstance(date: LocalDate? = null) = TimetableFragment().apply { + arguments = date?.let { bundleOf(ARGUMENT_DATE_KEY to it.toEpochDay()) } + } } override val titleStringId get() = R.string.timetable_title - override val isViewEmpty get() = timetableAdapter.isEmpty + override val isViewEmpty get() = timetableAdapter.itemCount == 0 override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timetable, container, false) - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableBinding.bind(view) + messageContainer = binding.timetableRecycler - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = timetableRecycler - presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) + val initDate = savedInstanceState?.getLong(SAVED_DATE_KEY) + ?: arguments?.getLong(ARGUMENT_DATE_KEY)?.takeUnless { it == 0L } + + presenter.onAttachView(this, initDate) } override fun initView() { - timetableAdapter.setOnItemClickListener(presenter::onTimetableItemSelected) - - with(timetableRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.timetableRecycler) { + layoutManager = LinearLayoutManager(context) adapter = timetableAdapter - addItemDecoration(FlexibleItemDecoration(context) - .withDefaultDivider() - .withDrawDividerOnLastItem(false) - ) + addItemDecoration(DividerItemDecoration(context)) } - timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - timetableErrorRetry.setOnClickListener { presenter.onRetry() } - timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + timetableSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + timetableSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + timetableErrorRetry.setOnClickListener { presenter.onRetry() } + timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() } - timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } - timetableNavDate.setOnClickListener { presenter.onPickDate() } - timetableNextButton.setOnClickListener { presenter.onNextDay() } + timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } + timetableNavDate.setOnClickListener { presenter.onPickDate() } + timetableNextButton.setOnClickListener { presenter.onNextDay() } - timetableNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + timetableNavContainer.elevation = requireContext().dpToPx(3f) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -91,28 +101,34 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return if (item.itemId == R.id.timetableMenuCompletedLessons) presenter.onCompletedLessonsSwitchSelected() - else false + return when (item.itemId) { + R.id.timetableMenuAdditionalLessons -> presenter.onAdditionalLessonsSwitchSelected() + R.id.timetableMenuCompletedLessons -> presenter.onCompletedLessonsSwitchSelected() + else -> false + } } - override fun updateData(data: List) { - timetableAdapter.updateDataSet(data, true) + override fun updateData(data: List, isDayChanged: Boolean) { + when { + isDayChanged -> timetableAdapter.recreate(data) + else -> timetableAdapter.submitList(data) + } } override fun clearData() { - timetableAdapter.clear() + timetableAdapter.submitList(listOf()) } override fun updateNavigationDay(date: String) { - timetableNavDate.text = date + binding.timetableNavDate.text = date } - override fun hideRefresh() { - timetableSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.timetableSwipe.isRefreshing = show } override fun resetView() { - timetableRecycler.smoothScrollToPosition(0) + binding.timetableRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { @@ -124,54 +140,63 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun showEmpty(show: Boolean) { - timetableEmpty.visibility = if (show) VISIBLE else GONE + binding.timetableEmpty.visibility = if (show) VISIBLE else GONE + } + + override fun setDayHeaderMessage(message: String?) { + binding.timetableEmptyMessage.visibility = if (message.isNullOrEmpty()) GONE else VISIBLE + binding.timetableEmptyMessage.text = message.orEmpty().parseAsHtml() } override fun showErrorView(show: Boolean) { - timetableError.visibility = if (show) VISIBLE else GONE + binding.timetableError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - timetableErrorMessage.text = message + binding.timetableErrorMessage.text = message } override fun showProgress(show: Boolean) { - timetableProgress.visibility = if (show) VISIBLE else GONE + binding.timetableProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - timetableSwipe.isEnabled = enable + binding.timetableSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - timetableRecycler.visibility = if (show) VISIBLE else GONE + binding.timetableRecycler.visibility = if (show) VISIBLE else GONE + } + + override fun showNavigation(show: Boolean) { + binding.timetableNavContainer.isVisible = true } override fun showPreButton(show: Boolean) { - timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showNextButton(show: Boolean) { - timetableNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + binding.timetableNextButton.visibility = if (show) VISIBLE else View.INVISIBLE } override fun showTimetableDialog(lesson: Timetable) { (activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson)) } - override fun showDatePickerDialog(currentDate: LocalDate) { - val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth -> - presenter.onDateSet(year, month + 1, dayOfMonth) - } - val datePickerDialog = DatePickerDialog.newInstance(dateSetListener, - currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth) + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) + } - with(datePickerDialog) { - setDateRangeLimiter(SchooldaysRangeLimiter()) - version = DatePickerDialog.Version.VERSION_2 - scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL - show(this@TimetableFragment.parentFragmentManager, null) - } + override fun openAdditionalLessonsView() { + (activity as? MainActivity)?.pushView(AdditionalLessonsFragment.newInstance()) } override fun openCompletedLessonsView() { @@ -180,7 +205,9 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + presenter.currentDate?.toEpochDay()?.let { + outState.putLong(SAVED_DATE_KEY, it) + } } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt index 5b35b85d..93290ba2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt @@ -1,197 +1,44 @@ package io.github.wulkanowy.ui.modules.timetable -import android.annotation.SuppressLint -import android.graphics.Paint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.widget.TextView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_timetable.* -import kotlinx.android.synthetic.main.item_timetable_small.* +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import java.time.Duration -class TimetableItem(val lesson: Timetable, private val showWholeClassPlan: String) : - AbstractFlexibleItem() { +sealed class TimetableItem(val type: TimetableItemType) { - override fun getLayoutRes() = when { - showWholeClassPlan == "small" && !lesson.isStudentPlan -> R.layout.item_timetable_small - else -> R.layout.item_timetable - } + data class Small( + val lesson: Timetable, + val isLessonNumberVisible: Boolean, + val onClick: (Timetable) -> Unit, + ) : TimetableItem(TimetableItemType.SMALL) - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { - return ViewHolder(view, adapter) - } + data class Normal( + val lesson: Timetable, + val showGroupsInPlan: Boolean, + val isLessonNumberVisible: Boolean, + val timeLeft: TimeLeft?, + val onClick: (Timetable) -> Unit, + ) : TimetableItem(TimetableItemType.NORMAL) - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList?) { - when (itemViewType) { - R.layout.item_timetable_small -> bindSmallView(holder) - R.layout.item_timetable -> bindNormalView(holder) - } - } + data class Empty( + val numFrom: Int, + val numTo: Int + ) : TimetableItem(TimetableItemType.EMPTY) - private fun bindSmallView(holder: ViewHolder) { - with(holder) { - timetableSmallItemNumber.text = lesson.number.toString() - timetableSmallItemSubject.text = lesson.subject - timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm") - timetableSmallItemRoom.text = lesson.room - timetableSmallItemTeacher.text = lesson.teacher - - updateSubjectStyle(timetableSmallItemSubject) - updateSmallDescription(this) - updateSmallColors(this) - } - } - - private fun bindNormalView(holder: ViewHolder) { - with(holder) { - timetableItemNumber.text = lesson.number.toString() - timetableItemSubject.text = lesson.subject - timetableItemRoom.text = lesson.room - timetableItemTeacher.text = lesson.teacher - timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm") - timetableItemTimeFinish.text = lesson.end.toFormattedString("HH:mm") - - updateSubjectStyle(timetableItemSubject) - updateNormalDescription(this) - updateNormalColors(this) - } - } - - private fun updateSubjectStyle(subjectView: TextView) { - subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG - else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() - } - - private fun updateSmallDescription(holder: ViewHolder) { - with(holder) { - if (lesson.info.isNotBlank() && !lesson.changes) { - timetableSmallItemDescription.visibility = VISIBLE - timetableSmallItemDescription.text = lesson.info - - timetableSmallItemRoom.visibility = GONE - timetableSmallItemTeacher.visibility = GONE - - timetableSmallItemDescription.setTextColor(holder.view.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary - else R.attr.colorTimetableChange - )) - } else { - timetableSmallItemDescription.visibility = GONE - timetableSmallItemRoom.visibility = VISIBLE - timetableSmallItemTeacher.visibility = VISIBLE - } - } - } - - private fun updateNormalDescription(holder: ViewHolder) { - with(holder) { - if (lesson.info.isNotBlank() && !lesson.changes) { - timetableItemDescription.visibility = VISIBLE - timetableItemDescription.text = lesson.info - - timetableItemRoom.visibility = GONE - timetableItemTeacher.visibility = GONE - - timetableItemDescription.setTextColor(holder.view.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary - else R.attr.colorTimetableChange - )) - } else { - timetableItemDescription.visibility = GONE - timetableItemRoom.visibility = VISIBLE - timetableItemTeacher.visibility = VISIBLE - } - } - } - - private fun updateSmallColors(holder: ViewHolder) { - with(holder) { - if (lesson.canceled) { - updateNumberAndSubjectCanceledColor(timetableSmallItemNumber, timetableSmallItemSubject) - } else { - updateNumberColor(timetableSmallItemNumber) - updateSubjectColor(timetableSmallItemSubject) - updateRoomColor(timetableSmallItemRoom) - updateTeacherColor(timetableSmallItemTeacher) - } - } - } - - private fun updateNormalColors(holder: ViewHolder) { - with(holder) { - if (lesson.canceled) { - updateNumberAndSubjectCanceledColor(timetableItemNumber, timetableItemSubject) - } else { - updateNumberColor(timetableItemNumber) - updateSubjectColor(timetableItemSubject) - updateRoomColor(timetableItemRoom) - updateTeacherColor(timetableItemTeacher) - } - } - } - - private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorPrimary)) - subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorPrimary)) - } - - private fun updateNumberColor(numberView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor( - if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - } - - private fun updateSubjectColor(subjectView: TextView) { - subjectView.setTextColor(subjectView.context.getThemeAttrColor( - if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - } - - private fun updateRoomColor(roomView: TextView) { - roomView.setTextColor(roomView.context.getThemeAttrColor( - if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange - else android.R.attr.textColorSecondary - )) - } - - private fun updateTeacherColor(teacherTextView: TextView) { - teacherTextView.setTextColor(teacherTextView.context.getThemeAttrColor( - if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange - else android.R.attr.textColorSecondary - )) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableItem - - if (lesson != other.lesson) return false - return true - } - - override fun hashCode(): Int { - var result = lesson.hashCode() - result = 31 * result + lesson.id.toInt() - return result - } - - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : - FlexibleViewHolder(view, adapter), LayoutContainer { - override val containerView: View - get() = contentView - } + data class Additional( + val additional: TimetableAdditional, + ) : TimetableItem(TimetableItemType.ADDITIONAL) +} + +data class TimeLeft( + val until: Duration?, + val left: Duration?, + val isJustFinished: Boolean, +) + +enum class TimetableItemType { + SMALL, + NORMAL, + EMPTY, + ADDITIONAL, } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 2e9d0a0b..c00bdc3e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,79 +1,114 @@ package io.github.wulkanowy.ui.modules.timetable -import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import android.os.Handler +import android.os.Looper +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.BELOW +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.NONE +import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS +import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS +import io.github.wulkanowy.data.enums.TimetableMode +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.pojos.TimetableFull +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase +import io.github.wulkanowy.domain.timetable.IsWeekendHasLessonsUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.isJustFinished +import io.github.wulkanowy.utils.isShowTimeUntil +import io.github.wulkanowy.utils.left import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.of -import org.threeten.bp.LocalDate.ofEpochDay +import io.github.wulkanowy.utils.until import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.LocalDate.of +import java.time.LocalDate.ofEpochDay +import java.util.Timer import javax.inject.Inject +import kotlin.concurrent.timer class TimetablePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, + private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase, private val semesterRepository: SemesterRepository, private val prefRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(errorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper, +) : BasePresenter(errorHandler, studentRepository) { - private var baseDate: LocalDate = now().nextOrSameSchoolDay + private var initialDate: LocalDate? = null + private var isWeekendHasLessons: Boolean = false + private var isEduOne: Boolean = false - lateinit var currentDate: LocalDate + var currentDate: LocalDate? = null private set private lateinit var lastError: Throwable + private var tickTimer: Timer? = null + fun onAttachView(view: TimetableView, date: Long?) { super.onAttachView(view) view.initView() Timber.i("Timetable was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(ofEpochDay(date ?: baseDate.toEpochDay())) - if (currentDate.isHolidays) setBaseDateOnHolidays() - reloadView() + currentDate = date?.let(::ofEpochDay) + loadData() } fun onPreviousDay() { - loadData(currentDate.previousSchoolDay) - reloadView() + val date = if (isWeekendHasLessons) { + currentDate?.minusDays(1) + } else currentDate?.previousSchoolDay + + reloadView(date ?: return) + loadData(isDayChanged = true) } fun onNextDay() { - loadData(currentDate.nextSchoolDay) - reloadView() + val date = if (isWeekendHasLessons) { + currentDate?.plusDays(1) + } else currentDate?.nextSchoolDay + + reloadView(date ?: return) + loadData(isDayChanged = true) } fun onPickDate() { - view?.showDatePickerDialog(currentDate) + view?.showDatePickerDialog(currentDate ?: return) } fun onDateSet(year: Int, month: Int, day: Int) { - loadData(of(year, month, day)) - reloadView() + reloadView(of(year, month, day)) + loadData() } fun onSwipeRefresh() { Timber.i("Force refreshing the timetable") - loadData(currentDate, true) + loadData(forceRefresh = true) } fun onRetry() { @@ -81,7 +116,7 @@ class TimetablePresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(currentDate, true) + loadData(forceRefresh = true) } fun onDetailsClick() { @@ -90,23 +125,23 @@ class TimetablePresenter @Inject constructor( fun onViewReselected() { Timber.i("Timetable view is reselected") - view?.also { view -> + view?.let { view -> if (view.currentStackSize == 1) { - baseDate.also { - if (currentDate != it) { - loadData(it) - reloadView() - } else if (!view.isViewEmpty) view.resetView() + if (currentDate != initialDate) { + reloadView(initialDate ?: return) + loadData() + } else if (!view.isViewEmpty) { + view.resetView() } - } else view.popView() + } else { + view.popView() + } } } - fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is TimetableItem) { - Timber.i("Select timetable item ${item.lesson.id}") - view?.showTimetableDialog(item.lesson) - } + fun onAdditionalLessonsSwitchSelected(): Boolean { + view?.openAdditionalLessonsView() + return true } fun onCompletedLessonsSwitchSelected(): Boolean { @@ -114,57 +149,197 @@ class TimetablePresenter @Inject constructor( return true } - private fun setBaseDateOnHolidays() { - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + private fun loadData(forceRefresh: Boolean = false, isDayChanged: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + + isEduOne = student.isEduOne == true + checkInitialAndCurrentDate(semester) + timetableRepository.getTimetable( + student = student, + semester = semester, + start = currentDate ?: now(), + end = currentDate ?: now(), + forceRefresh = forceRefresh, + timetableType = TimetableRepository.TimetableType.NORMAL + ) + } + .logResourceStatus("load timetable data") + .onResourceData { + isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessonsUseCase(it.lessons) + + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + updateData(it, isDayChanged) + showContent(it.lessons.isNotEmpty() || it.additional.isNotEmpty()) + showEmpty(it.lessons.isEmpty() && it.additional.isEmpty()) + setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content) + reloadNavigation() + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "timetable", + "items" to it.lessons.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } - private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading timetable data started") - currentDate = date - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - timetableRepository.getTimetable(student, semester, currentDate, currentDate, forceRefresh) - } - } - .delay(200, MILLISECONDS) - .map { createTimetableItems(it) } - .map { items -> items.sortedWith(compareBy({ it.lesson.number }, { !it.lesson.isStudentPlan })) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading timetable result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - } - analytics.logEvent("load_timetable", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading timetable result: An exception occurred") - errorHandler.dispatch(it) - }) + private suspend fun checkInitialAndCurrentDate(semester: Semester) { + if (initialDate == null) { + isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester) + initialDate = getInitialDate(semester) } + + if (currentDate == null) { + currentDate = initialDate + } + } + + private fun getInitialDate(semester: Semester): LocalDate { + val now = now() + + return when { + now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear) + isWeekendHasLessons -> now + else -> now.nextOrSameSchoolDay + } + } + + private fun updateData(lessons: TimetableFull, isDayChanged: Boolean) { + tickTimer?.cancel() + + view?.updateData(createItems(lessons), isDayChanged) + if (currentDate == now()) { + tickTimer = timer(period = 2_000, initialDelay = 2_000) { + Handler(Looper.getMainLooper()).post { + view?.updateData(createItems(lessons), isDayChanged) + } + } + } + } + + private sealed class Item( + val isStudentPlan: Boolean, + val start: Instant, + val number: Int?, + ) { + class Lesson(val lesson: Timetable) : + Item(lesson.isStudentPlan, lesson.start, lesson.number) + + class Additional(val additional: TimetableAdditional) : Item(true, additional.start, null) + } + + private fun createItems(fullTimetable: TimetableFull): List { + val showAdditionalLessonsInPlan = prefRepository.showAdditionalLessonsInPlan + val allItems = + fullTimetable.lessons.map(Item::Lesson) + fullTimetable.additional.map(Item::Additional) + .takeIf { showAdditionalLessonsInPlan != NONE }.orEmpty() + + val filteredItems = allItems.filter { + if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { + it.isStudentPlan + } else true + }.sortedWith( + (compareBy { it is Item.Additional } + .takeIf { showAdditionalLessonsInPlan == BELOW } ?: EmptyComparator()) + .thenBy { it.start } + .thenBy { !it.isStudentPlan } + ) + + var prevNum = when (prefRepository.showTimetableGaps) { + BETWEEN_AND_BEFORE_LESSONS -> 0 + else -> null + } + var prevIsAdditional = false + return buildList { + filteredItems.forEachIndexed { i, it -> + if (prefRepository.showTimetableGaps != NO_GAPS) { + if (prevNum != null && it.number != null && it.number > prevNum!! + 1) { + if (!prevIsAdditional) { + // Additional lessons do count as a lesson so don't add empty lessons + // when there is an additional lesson present + val emptyLesson = TimetableItem.Empty( + numFrom = prevNum!! + 1, numTo = it.number - 1 + ) + add(emptyLesson) + } + } + prevNum = it.number + prevIsAdditional = it is Item.Additional + } + + if (it is Item.Lesson) { + if (it.isStudentPlan) { + val normalLesson = TimetableItem.Normal( + lesson = it.lesson, + showGroupsInPlan = prefRepository.showGroupsInPlan, + timeLeft = filteredItems.getTimeLeftForLesson(it.lesson, i), + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne + ) + add(normalLesson) + } else { + val smallLesson = TimetableItem.Small( + lesson = it.lesson, + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne + ) + add(smallLesson) + } + } else if (it is Item.Additional) { + // If the user disabled showing additional lessons, they would've been filtered + // out already, so there's no need to check it again. + add(TimetableItem.Additional(it.additional)) + } + } + } + } + + private fun List.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft { + val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index)) + return TimeLeft( + until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil }, + left = lesson.left?.plusMinutes(1), + isJustFinished = lesson.isJustFinished, + ) + } + + private fun List.getPreviousLesson(position: Int): Instant? { + val lessonAdditionalOffset = filterIndexed { i, item -> + i < position && item is Item.Additional + }.size + val lessonStudentPlanOffset = filterIndexed { i, item -> + i < position && !item.isStudentPlan + }.size + val lessonIndex = position - 1 - lessonAdditionalOffset - lessonStudentPlanOffset + + return filterIsInstance() + .filter { it.isStudentPlan } + .getOrNull(lessonIndex) + ?.let { + if (!it.lesson.canceled && it.isStudentPlan) it.lesson.end + else null + } + } + + private fun onTimetableItemSelected(lesson: Timetable) { + Timber.i("Select timetable item ${lesson.id}") + view?.showTimetableDialog(lesson) } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -178,14 +353,10 @@ class TimetablePresenter @Inject constructor( } } - private fun createTimetableItems(items: List): List { - return items - .filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } - .map { TimetableItem(it, prefRepository.showWholeClassPlan) } - } + private fun reloadView(date: LocalDate) { + currentDate = date - private fun reloadView() { - Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}") + Timber.i("Reload timetable view with the date ${currentDate?.toFormattedString()}") view?.apply { showProgress(true) enableSwipe(false) @@ -197,12 +368,24 @@ class TimetablePresenter @Inject constructor( } } - @SuppressLint("DefaultLocale") private fun reloadNavigation() { + val currentDate = currentDate ?: return + view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays) - updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize()) + updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) + showNavigation(true) } } + + override fun onDetachView() { + tickTimer?.cancel() + tickTimer = null + super.onDetachView() + } +} + +private class EmptyComparator : Comparator { + override fun compare(o1: T, o2: T) = 0 } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index f730a271..f4d5b762 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.ui.base.BaseView -import org.threeten.bp.LocalDate +import java.time.LocalDate interface TimetableView : BaseView { @@ -12,18 +12,20 @@ interface TimetableView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List, isDayChanged: Boolean) fun updateNavigationDay(date: String) fun clearData() - fun hideRefresh() + fun showRefresh(show: Boolean) fun resetView() fun showEmpty(show: Boolean) + fun setDayHeaderMessage(message: String?) + fun showErrorView(show: Boolean) fun setErrorDetails(message: String) @@ -34,15 +36,19 @@ interface TimetableView : BaseView { fun showContent(show: Boolean) + fun showNavigation(show: Boolean) + fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) fun showTimetableDialog(lesson: Timetable) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun popView() + fun openAdditionalLessonsView() + fun openCompletedLessonsView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt new file mode 100644 index 00000000..c2ce8028 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.databinding.ItemTimetableAdditionalBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class AdditionalLessonsAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onDeleteClickListener: (timetableAdditional: TimetableAdditional) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemTimetableAdditionalBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + additionalLessonItemTime.text = + "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}" + additionalLessonItemSubject.text = item.subject + + additionalLessonItemDelete.isVisible = item.isAddedByUser + additionalLessonItemDelete.setOnClickListener { onDeleteClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemTimetableAdditionalBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt new file mode 100644 index 00000000..bf6be56f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -0,0 +1,181 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.databinding.FragmentTimetableAdditionalBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class AdditionalLessonsFragment : + BaseFragment(R.layout.fragment_timetable_additional), + AdditionalLessonsView, MainView.TitledView { + + @Inject + lateinit var presenter: AdditionalLessonsPresenter + + @Inject + lateinit var additionalLessonsAdapter: AdditionalLessonsAdapter + + companion object { + private const val SAVED_DATE_KEY = "CURRENT_DATE" + + fun newInstance() = AdditionalLessonsFragment() + } + + override val titleStringId get() = R.string.additional_lessons_title + + override val isViewEmpty get() = additionalLessonsAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableAdditionalBinding.bind(view) + messageContainer = binding.additionalLessonsRecycler + presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) + } + + override fun initView() { + with(binding.additionalLessonsRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = additionalLessonsAdapter.apply { + onDeleteClickListener = { presenter.onDeleteLessonsSelected(it) } + } + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + additionalLessonsSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() } + additionalLessonsErrorDetails.setOnClickListener { presenter.onDetailsClick() } + + additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } + additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() } + additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() } + + openAddAdditionalLessonButton.setOnClickListener { presenter.onAdditionalLessonAddButtonClicked() } + + additionalLessonsNavContainer.elevation = requireContext().dpToPx(3f) + } + } + + override fun updateData(data: List) { + with(additionalLessonsAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(additionalLessonsAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showSuccessMessage() { + getString(R.string.additional_lessons_delete_success) + } + + override fun updateNavigationDay(date: String) { + binding.additionalLessonsNavDate.text = date + } + + override fun hideRefresh() { + binding.additionalLessonsSwipe.isRefreshing = false + } + + override fun showEmpty(show: Boolean) { + binding.additionalLessonsEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.additionalLessonsError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.additionalLessonsErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.additionalLessonsProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun enableSwipe(enable: Boolean) { + binding.additionalLessonsSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.additionalLessonsRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showPreButton(show: Boolean) { + binding.additionalLessonsPreviousButton.visibility = + if (show) View.VISIBLE else View.INVISIBLE + } + + override fun showNextButton(show: Boolean) { + binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE + } + + override fun showAddAdditionalLessonDialog(currentDate: LocalDate) { + (activity as? MainActivity)?.showDialogFragment( + AdditionalLessonAddDialog.newInstance( + currentDate + ) + ) + } + + override fun showDatePickerDialog(selectedDate: LocalDate) { + val now = LocalDate.now() + + openMaterialDatePicker( + selected = selectedDate, + rangeStart = now.firstSchoolDayInSchoolYear, + rangeEnd = now.lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) + } + + override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.additional_lessons_delete_title)) + .setItems( + arrayOf( + getString(R.string.additional_lessons_delete_one), + getString(R.string.additional_lessons_delete_series) + ) + ) { _, position -> presenter.onDeleteDialogSelectItem(position, timetableAdditional) } + .show() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt new file mode 100644 index 00000000..16ec9746 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -0,0 +1,227 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.annotation.SuppressLint +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import timber.log.Timber +import java.time.LocalDate +import javax.inject.Inject + +class AdditionalLessonsPresenter @Inject constructor( + studentRepository: StudentRepository, + errorHandler: ErrorHandler, + private val semesterRepository: SemesterRepository, + private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay + + private var isWeekendHasLessons: Boolean = false + + lateinit var currentDate: LocalDate + private set + + private lateinit var lastError: Throwable + + fun onAttachView(view: AdditionalLessonsView, date: Long?) { + super.onAttachView(view) + view.initView() + Timber.i("Additional lessons was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData(LocalDate.ofEpochDay(date ?: baseDate.toEpochDay())) + if (currentDate.isHolidays) setBaseDateOnHolidays() + reloadView() + } + + fun onPreviousDay() { + val date = if (isWeekendHasLessons) { + currentDate.minusDays(1) + } else currentDate.previousSchoolDay + loadData(date) + reloadView() + } + + fun onNextDay() { + val date = if (isWeekendHasLessons) { + currentDate.plusDays(1) + } else currentDate.nextSchoolDay + loadData(date) + reloadView() + } + + fun onPickDate() { + view?.showDatePickerDialog(currentDate) + } + + fun onAdditionalLessonAddButtonClicked() { + view?.showAddAdditionalLessonDialog(currentDate) + } + + fun onDateSet(year: Int, month: Int, day: Int) { + loadData(LocalDate.of(year, month, day)) + reloadView() + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the additional lessons") + loadData(currentDate, true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(currentDate, true) + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun setBaseDateOnHolidays() { + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") + } + + fun onDeleteLessonsSelected(timetableAdditional: TimetableAdditional) { + if (timetableAdditional.repeatId == null) { + deleteAdditionalLessons(timetableAdditional, false) + } else { + view?.showDeleteLessonDialog(timetableAdditional) + } + } + + fun onDeleteDialogSelectItem(position: Int, timetableAdditional: TimetableAdditional) { + deleteAdditionalLessons(timetableAdditional, position == 1) + } + + private fun deleteAdditionalLessons( + timetableAdditional: TimetableAdditional, + deleteSeries: Boolean + ) { + presenterScope.launch { + Timber.i("Additional Lesson delete start") + runCatching { timetableRepository.deleteAdditional(timetableAdditional, deleteSeries) } + .onSuccess { + Timber.i("Additional Lesson delete: Success") + view?.showSuccessMessage() + } + .onFailure { + Timber.i("Additional Lesson delete result: An exception occurred") + errorHandler.dispatch(it) + } + } + } + + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + currentDate = date + + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + + isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester, currentDate) + timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = forceRefresh, + refreshAdditional = true, + timetableType = TimetableRepository.TimetableType.ADDITIONAL + ) + } + .logResourceStatus("load additional lessons") + .onResourceData { + view?.apply { + updateData(it.additional.sortedBy { item -> item.start }) + showEmpty(it.additional.isEmpty()) + showErrorView(false) + showContent(it.additional.isNotEmpty()) + } + } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "additional_lessons", + "items" to it.additional.size + ) + } + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun reloadView() { + Timber.i("Reload additional lessons view with the date ${currentDate.toFormattedString()}") + view?.apply { + showProgress(true) + enableSwipe(false) + showContent(false) + showEmpty(false) + showErrorView(false) + clearData() + reloadNavigation() + } + } + + @SuppressLint("DefaultLocale") + private fun reloadNavigation() { + view?.apply { + showPreButton(!currentDate.minusDays(1).isHolidays) + showNextButton(!currentDate.plusDays(1).isHolidays) + updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt new file mode 100644 index 00000000..291c1217 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate + +interface AdditionalLessonsView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun updateNavigationDay(date: String) + + fun hideRefresh() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) + + fun showPreButton(show: Boolean) + + fun showNextButton(show: Boolean) + + fun showDatePickerDialog(selectedDate: LocalDate) + + fun showAddAdditionalLessonDialog(currentDate: LocalDate) + + fun showSuccessMessage() + + fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt new file mode 100644 index 00000000..9470c910 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -0,0 +1,178 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.timepicker.MaterialTimePicker +import com.google.android.material.timepicker.TimeFormat +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogAdditionalAddBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import java.time.LocalTime +import javax.inject.Inject + +@AndroidEntryPoint +class AdditionalLessonAddDialog : BaseDialogFragment(), + AdditionalLessonAddView { + + @Inject + lateinit var presenter: AdditionalLessonAddPresenter + + companion object { + const val ARGUMENT_KEY = "additional_lesson_default_date" + fun newInstance(defaultDate: LocalDate) = AdditionalLessonAddDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to defaultDate.toEpochDay()) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAdditionalAddBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + arguments?.getLong(ARGUMENT_KEY)?.let(LocalDate::ofEpochDay)?.let { + presenter.onDateSelected(it) + } + presenter.onAttachView(this) + } + + override fun initView(selectedDate: LocalDate) { + with(binding) { + additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogStart.isErrorEnabled = false + additionalLessonDialogStart.error = null + } + additionalLessonDialogEndEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogEnd.isErrorEnabled = false + additionalLessonDialogEnd.error = null + } + additionalLessonDialogDateEdit.setText(selectedDate.toFormattedString()) + additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogDate.isErrorEnabled = false + additionalLessonDialogDate.error = null + } + additionalLessonDialogContentEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogContent.isErrorEnabled = false + additionalLessonDialogContent.error = null + } + additionalLessonDialogAdd.setOnClickListener { + presenter.onAddAdditionalClicked( + start = additionalLessonDialogStartEdit.text?.toString(), + end = additionalLessonDialogEndEdit.text?.toString(), + date = additionalLessonDialogDateEdit.text?.toString(), + content = additionalLessonDialogContentEdit.text?.toString(), + isRepeat = additionalLessonDialogRepeat.isChecked + ) + } + additionalLessonDialogClose.setOnClickListener { dismiss() } + additionalLessonDialogDateEdit.setOnClickListener { presenter.showDatePicker() } + additionalLessonDialogStartEdit.setOnClickListener { presenter.showStartTimePicker() } + additionalLessonDialogEndEdit.setOnClickListener { presenter.showEndTimePicker() } + } + } + + override fun showSuccessMessage() { + showMessage(getString(R.string.additional_lessons_add_success)) + } + + override fun setErrorDateRequired() { + with(binding.additionalLessonDialogDate) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorStartRequired() { + with(binding.additionalLessonDialogStart) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorEndRequired() { + with(binding.additionalLessonDialogEnd) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorContentRequired() { + with(binding.additionalLessonDialogContent) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorIncorrectEndTime() { + with(binding.additionalLessonDialogEnd) { + isErrorEnabled = true + error = getString(R.string.additional_lessons_end_time_error) + } + } + + override fun closeDialog() { + dismiss() + } + + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = LocalDate.now(), + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSelected(it) + binding.additionalLessonDialogDateEdit.setText(it.toFormattedString()) + } + ) + } + + override fun showStartTimePickerDialog(selectedTime: LocalTime) { + showTimePickerDialog(selectedTime) { + presenter.onStartTimeSelected(it) + binding.additionalLessonDialogStartEdit.setText(it.toString()) + } + } + + override fun showEndTimePickerDialog(selectedTime: LocalTime) { + showTimePickerDialog(selectedTime) { + presenter.onEndTimeSelected(it) + binding.additionalLessonDialogEndEdit.setText(it.toString()) + } + } + + private fun showTimePickerDialog(defaultTime: LocalTime, onTimeSelected: (LocalTime) -> Unit) { + val timePicker = MaterialTimePicker.Builder() + .setTimeFormat(TimeFormat.CLOCK_24H) + .setHour(defaultTime.hour) + .setMinute(defaultTime.minute) + .build() + + timePicker.addOnPositiveButtonClickListener { + if (isAdded) { + onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute)) + } + } + + if (!parentFragmentManager.isStateSaved) { + timePicker.show(parentFragmentManager, null) + } + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt new file mode 100644 index 00000000..db59a2ab --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt @@ -0,0 +1,167 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.toLocalDate +import kotlinx.coroutines.launch +import timber.log.Timber +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import java.util.UUID +import javax.inject.Inject + +class AdditionalLessonAddPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val timetableRepository: TimetableRepository, + private val semesterRepository: SemesterRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var selectedStartTime = LocalTime.of(15, 0) + + private var selectedEndTime = LocalTime.of(15, 45) + + private var selectedDate = LocalDate.now() + + override fun onAttachView(view: AdditionalLessonAddView) { + super.onAttachView(view) + view.initView(selectedDate) + Timber.i("AdditionalLesson details view was initialized") + } + + fun showDatePicker() { + view?.showDatePickerDialog(selectedDate) + } + + fun showStartTimePicker() { + view?.showStartTimePickerDialog(selectedStartTime) + } + + fun showEndTimePicker() { + view?.showEndTimePickerDialog(selectedEndTime) + } + + fun onStartTimeSelected(time: LocalTime) { + selectedStartTime = time + } + + fun onEndTimeSelected(time: LocalTime) { + selectedEndTime = time + } + + fun onDateSelected(date: LocalDate) { + selectedDate = date + } + + fun onAddAdditionalClicked( + start: String?, + end: String?, + date: String?, + content: String?, + isRepeat: Boolean + ) { + if (isUserInputValid(start, end, date, content)) { + addAdditionalLesson( + start = LocalTime.parse(start!!), + end = LocalTime.parse(end), + date = date!!.toLocalDate(), + subject = content!!, + isRepeat = isRepeat + ) + } + } + + private fun isUserInputValid( + start: String?, + end: String?, + date: String?, + content: String? + ): Boolean { + var isValid = true + + if (start.isNullOrBlank()) { + view?.setErrorStartRequired() + isValid = false + } + + if (end.isNullOrBlank()) { + view?.setErrorEndRequired() + isValid = false + } + + if (date.isNullOrBlank()) { + view?.setErrorDateRequired() + isValid = false + } + + if (content.isNullOrBlank()) { + view?.setErrorContentRequired() + isValid = false + } + + if (selectedStartTime >= selectedEndTime) { + view?.setErrorIncorrectEndTime() + isValid = false + } + + return isValid + } + + private fun addAdditionalLesson( + start: LocalTime, + end: LocalTime, + date: LocalDate, + subject: String, + isRepeat: Boolean + ) { + presenterScope.launch { + val semester = runCatching { + val student = studentRepository.getCurrentStudent() + semesterRepository.getCurrentSemester(student) + } + .onFailure(errorHandler::dispatch) + .getOrNull() ?: return@launch + + val weeks = if (isRepeat) { + ChronoUnit.WEEKS.between(date, date.lastSchoolDayInSchoolYear) + } else 0 + val uniqueRepeatId = UUID.randomUUID().takeIf { isRepeat } + + val lessonsToAdd = (0..weeks).map { + TimetableAdditional( + studentId = semester.studentId, + diaryId = semester.diaryId, + start = ZonedDateTime.of(date, start, ZoneId.systemDefault()).toInstant(), + end = ZonedDateTime.of(date, end, ZoneId.systemDefault()).toInstant(), + date = date.plusWeeks(it), + subject = subject + ).apply { + isAddedByUser = true + repeatId = uniqueRepeatId + } + } + + Timber.i("AdditionalLesson insert start") + runCatching { timetableRepository.saveAdditionalList(lessonsToAdd) } + .onSuccess { + Timber.i("AdditionalLesson insert: Success") + view?.run { + showSuccessMessage() + closeDialog() + } + } + .onFailure { + Timber.i("AdditionalLesson insert result: An exception occurred") + errorHandler.dispatch(it) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt new file mode 100644 index 00000000..8d9678e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate +import java.time.LocalTime + +interface AdditionalLessonAddView : BaseView { + + fun initView(selectedDate: LocalDate) + + fun closeDialog() + + fun showDatePickerDialog(selectedDate: LocalDate) + + fun showStartTimePickerDialog(selectedTime: LocalTime) + + fun showEndTimePickerDialog(selectedTime: LocalTime) + + fun showSuccessMessage() + + fun setErrorDateRequired() + + fun setErrorStartRequired() + + fun setErrorEndRequired() + + fun setErrorContentRequired() + + fun setErrorIncorrectEndTime() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt index 8f7b1ec5..d937d4dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt @@ -1,73 +1,82 @@ package io.github.wulkanowy.ui.modules.timetable.completed -import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import io.github.wulkanowy.R +import androidx.core.os.bundleOf +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.CompletedLesson -import kotlinx.android.synthetic.main.dialog_lesson_completed.* +import io.github.wulkanowy.databinding.DialogLessonCompletedBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.serializable -class CompletedLessonDialog : DialogFragment() { +@AndroidEntryPoint +class CompletedLessonDialog : BaseDialogFragment() { private lateinit var completedLesson: CompletedLesson companion object { + private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: CompletedLesson): CompletedLessonDialog { - return CompletedLessonDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } - } + fun newInstance(lesson: CompletedLesson) = CompletedLessonDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to lesson) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - completedLesson = getSerializable(CompletedLessonDialog.ARGUMENT_KEY) as CompletedLesson + completedLesson = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogLessonCompletedBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + completedLessonDialogSubjectValue.text = completedLesson.subject + completedLessonDialogTopicValue.text = completedLesson.topic + completedLessonDialogTeacherValue.text = completedLesson.teacher + completedLessonDialogAbsenceValue.text = completedLesson.absence + completedLessonDialogChangesValue.text = completedLesson.substitution + completedLessonDialogResourcesValue.text = completedLesson.resources } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_lesson_completed, container, false) - } - - @SuppressLint("SetTextI18n") - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - completedLessonDialogSubject.text = completedLesson.subject - completedLessonDialogTopic.text = completedLesson.topic - completedLessonDialogTeacher.text = completedLesson.teacher - completedLessonDialogAbsence.text = completedLesson.absence - completedLessonDialogChanges.text = completedLesson.substitution - completedLessonDialogResources.text = completedLesson.resources completedLesson.substitution.let { if (it.isBlank()) { - completedLessonDialogChangesTitle.visibility = View.GONE - completedLessonDialogChanges.visibility = View.GONE - } else completedLessonDialogChanges.text = it + with(binding) { + completedLessonDialogChangesTitle.visibility = View.GONE + completedLessonDialogChangesValue.visibility = View.GONE + } + } else binding.completedLessonDialogChangesValue.text = it } completedLesson.absence.let { if (it.isBlank()) { - completedLessonDialogAbsenceTitle.visibility = View.GONE - completedLessonDialogAbsence.visibility = View.GONE - } else completedLessonDialogAbsence.text = it + with(binding) { + completedLessonDialogAbsenceTitle.visibility = View.GONE + completedLessonDialogAbsenceValue.visibility = View.GONE + } + } else binding.completedLessonDialogAbsenceValue.text = it } completedLesson.resources.let { if (it.isBlank()) { - completedLessonDialogResourcesTitle.visibility = View.GONE - completedLessonDialogResources.visibility = View.GONE - } else completedLessonDialogResources.text = it + with(binding) { + completedLessonDialogResourcesTitle.visibility = View.GONE + completedLessonDialogResourcesValue.visibility = View.GONE + } + } else binding.completedLessonDialogResourcesValue.text = it } - completedLessonDialogClose.setOnClickListener { dismiss() } + binding.completedLessonDialogClose.setOnClickListener { dismiss() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt deleted file mode 100644 index fd6dc8a6..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonItem.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.wulkanowy.ui.modules.timetable.completed - -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.viewholders.FlexibleViewHolder -import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.utils.getThemeAttrColor -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_completed_lesson.* - -class CompletedLessonItem(val completedLesson: CompletedLesson) : AbstractFlexibleItem() { - - override fun getLayoutRes() = R.layout.item_completed_lesson - - override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): CompletedLessonItem.ViewHolder { - return CompletedLessonItem.ViewHolder(view, adapter) - } - - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: CompletedLessonItem.ViewHolder?, position: Int, payloads: MutableList?) { - holder?.apply { - completedLessonItemNumber.text = completedLesson.number.toString() - completedLessonItemNumber.setTextColor(holder.contentView.context.getThemeAttrColor( - if (completedLesson.substitution.isNotEmpty()) R.attr.colorTimetableChange - else android.R.attr.textColorPrimary - )) - completedLessonItemSubject.text = completedLesson.subject - completedLessonItemTopic.text = completedLesson.topic - completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as CompletedLessonItem - - if (completedLesson != other.completedLesson) return false - - return true - } - - override fun hashCode(): Int { - return completedLesson.hashCode() - } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? - get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt new file mode 100644 index 00000000..3399a8a2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsAdapter.kt @@ -0,0 +1,45 @@ +package io.github.wulkanowy.ui.modules.timetable.completed + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.databinding.ItemCompletedLessonBinding +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +class CompletedLessonsAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var onClickListener: (CompletedLesson) -> Unit = {} + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemCompletedLessonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + completedLessonItemNumber.text = item.number.toString() + completedLessonItemNumber.setTextColor(root.context.getThemeAttrColor( + if (item.substitution.isNotEmpty()) R.attr.colorTimetableChange + else android.R.attr.textColorPrimary + )) + completedLessonItemSubject.text = item.subject + completedLessonItemTopic.text = item.topic + completedLessonItemAlert.visibility = if (item.substitution.isNotEmpty()) View.VISIBLE else View.GONE + + root.setOnClickListener { onClickListener(item) } + } + } + + class ItemViewHolder(val binding: ItemCompletedLessonBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt index c1389ced..36e38fb9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.ui.modules.timetable.completed -import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector -import io.github.wulkanowy.sdk.exception.FeatureDisabledException +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class CompletedLessonsErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { +class CompletedLessonsErrorHandler @Inject constructor(@ApplicationContext context: Context) : + ErrorHandler(context) { var onFeatureDisabled: () -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 60c16b2d..77a7bbd5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -1,36 +1,31 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import android.view.ViewGroup -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import android.view.View.* +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.databinding.FragmentTimetableCompletedBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.SchooldaysRangeLimiter -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.fragment_timetable_completed.* -import org.threeten.bp.LocalDate +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.* +import java.time.LocalDate import javax.inject.Inject -class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView.TitledView { +@AndroidEntryPoint +class CompletedLessonsFragment : + BaseFragment(R.layout.fragment_timetable_completed), + CompletedLessonsView, MainView.TitledView { @Inject lateinit var presenter: CompletedLessonsPresenter @Inject - lateinit var completedLessonsAdapter: FlexibleAdapter> + lateinit var completedLessonsAdapter: CompletedLessonsAdapter companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" @@ -40,107 +35,121 @@ class CompletedLessonsFragment : BaseFragment(), CompletedLessonsView, MainView. override val titleStringId get() = R.string.completed_lessons_title - override val isViewEmpty get() = completedLessonsAdapter.isEmpty + override val isViewEmpty get() = completedLessonsAdapter.items.isEmpty() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timetable_completed, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - messageContainer = completedLessonsRecycler + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableCompletedBinding.bind(view) + messageContainer = binding.completedLessonsRecycler presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { - completedLessonsAdapter.setOnItemClickListener(presenter::onCompletedLessonsItemSelected) + completedLessonsAdapter.onClickListener = presenter::onCompletedLessonsItemSelected - with(completedLessonsRecycler) { - layoutManager = SmoothScrollLinearLayoutManager(context) + with(binding.completedLessonsRecycler) { + layoutManager = LinearLayoutManager(context) adapter = completedLessonsAdapter + addItemDecoration(DividerItemDecoration(context)) } - completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) - completedLessonErrorRetry.setOnClickListener { presenter.onRetry() } - completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() } + with(binding) { + completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + completedLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + completedLessonsSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) + ) + completedLessonErrorRetry.setOnClickListener { presenter.onRetry() } + completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() } - completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } - completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } - completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } + completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } + completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } + completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } - completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + completedLessonsNavContainer.elevation = requireContext().dpToPx(3f) + } } - override fun updateData(data: List) { - completedLessonsAdapter.updateDataSet(data, true) + override fun updateData(data: List) { + with(completedLessonsAdapter) { + items = data + notifyDataSetChanged() + } } override fun clearData() { - completedLessonsAdapter.clear() + with(completedLessonsAdapter) { + items = emptyList() + notifyDataSetChanged() + } } override fun updateNavigationDay(date: String) { - completedLessonsNavDate.text = date + binding.completedLessonsNavDate.text = date } - override fun hideRefresh() { - completedLessonsSwipe.isRefreshing = false + override fun showRefresh(show: Boolean) { + binding.completedLessonsSwipe.isRefreshing = show } override fun showEmpty(show: Boolean) { - completedLessonsEmpty.visibility = if (show) VISIBLE else GONE + binding.completedLessonsEmpty.visibility = if (show) VISIBLE else GONE } override fun showErrorView(show: Boolean) { - completedLessonError.visibility = if (show) VISIBLE else GONE + binding.completedLessonError.visibility = if (show) VISIBLE else GONE } override fun setErrorDetails(message: String) { - completedLessonErrorMessage.text = message + binding.completedLessonErrorMessage.text = message } override fun showFeatureDisabled() { - completedLessonsInfo.text = getString(R.string.error_feature_disabled) - completedLessonsInfoImage.setImageDrawable(requireContext().getCompatDrawable(R.drawable.ic_all_close_circle)) + with(binding) { + completedLessonsInfo.text = getString(R.string.error_feature_disabled) + completedLessonsInfoImage.setImageDrawable(requireContext().getCompatDrawable(R.drawable.ic_all_close_circle)) + } } override fun showProgress(show: Boolean) { - completedLessonsProgress.visibility = if (show) VISIBLE else GONE + binding.completedLessonsProgress.visibility = if (show) VISIBLE else GONE } override fun enableSwipe(enable: Boolean) { - completedLessonsSwipe.isEnabled = enable + binding.completedLessonsSwipe.isEnabled = enable } override fun showContent(show: Boolean) { - completedLessonsRecycler.visibility = if (show) VISIBLE else GONE + binding.completedLessonsRecycler.visibility = if (show) VISIBLE else GONE } override fun showPreButton(show: Boolean) { - completedLessonsPreviousButton.visibility = if (show) VISIBLE else INVISIBLE + binding.completedLessonsPreviousButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showNextButton(show: Boolean) { - completedLessonsNextButton.visibility = if (show) VISIBLE else INVISIBLE + binding.completedLessonsNextButton.visibility = if (show) VISIBLE else INVISIBLE } override fun showCompletedLessonDialog(completedLesson: CompletedLesson) { - (activity as? MainActivity)?.showDialogFragment(CompletedLessonDialog.newInstance(completedLesson)) + (activity as? MainActivity)?.showDialogFragment( + CompletedLessonDialog.newInstance( + completedLesson + ) + ) } - override fun showDatePickerDialog(currentDate: LocalDate) { - val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth -> - presenter.onDateSet(year, month + 1, dayOfMonth) - } - val datePickerDialog = DatePickerDialog.newInstance(dateSetListener, - currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth) + override fun showDatePickerDialog(selectedDate: LocalDate) { + val now = LocalDate.now() - with(datePickerDialog) { - setDateRangeLimiter(SchooldaysRangeLimiter()) - version = DatePickerDialog.Version.VERSION_2 - scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL - show(this@CompletedLessonsFragment.parentFragmentManager, null) - } + openMaterialDatePicker( + selected = selectedDate, + rangeStart = now.firstSchoolDayInSchoolYear, + rangeEnd = now.lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 001fed97..16c51fd2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -1,34 +1,29 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.repositories.CompletedLessonsRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now -import org.threeten.bp.LocalDate.ofEpochDay +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.util.concurrent.TimeUnit +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.LocalDate.ofEpochDay import javax.inject.Inject class CompletedLessonsPresenter @Inject constructor( - schedulers: SchedulersProvider, studentRepository: StudentRepository, private val completedLessonsErrorHandler: CompletedLessonsErrorHandler, private val semesterRepository: SemesterRepository, private val completedLessonsRepository: CompletedLessonsRepository, - private val analytics: FirebaseAnalyticsHelper -) : BasePresenter(completedLessonsErrorHandler, studentRepository, schedulers) { + private val analytics: AnalyticsHelper +) : BasePresenter(completedLessonsErrorHandler, studentRepository) { private var baseDate: LocalDate = now().nextOrSameSchoolDay @@ -44,21 +39,22 @@ class CompletedLessonsPresenter @Inject constructor( completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError completedLessonsErrorHandler.onFeatureDisabled = { this.view?.showFeatureDisabled() + this.view?.showEmpty(true) Timber.i("Completed lessons feature disabled by school") } - loadData(ofEpochDay(date ?: baseDate.toEpochDay())) + reloadView(ofEpochDay(date ?: baseDate.toEpochDay())) + loadData() if (currentDate.isHolidays) setBaseDateOnHolidays() - reloadView() } fun onPreviousDay() { - loadData(currentDate.previousSchoolDay) - reloadView() + reloadView(currentDate.previousSchoolDay) + loadData() } fun onNextDay() { - loadData(currentDate.nextSchoolDay) - reloadView() + reloadView(currentDate.nextSchoolDay) + loadData() } fun onPickDate() { @@ -66,13 +62,13 @@ class CompletedLessonsPresenter @Inject constructor( } fun onDateSet(year: Int, month: Int, day: Int) { - loadData(LocalDate.of(year, month, day)) - reloadView() + reloadView(LocalDate.of(year, month, day)) + loadData() } fun onSwipeRefresh() { Timber.i("Force refreshing the completed lessons") - loadData(currentDate, true) + loadData(true) } fun onRetry() { @@ -80,71 +76,72 @@ class CompletedLessonsPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(currentDate, true) + loadData(true) } fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } - fun onCompletedLessonsItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is CompletedLessonItem) { - Timber.i("Select completed lessons item ${item.completedLesson.id}") - view?.showCompletedLessonDialog(item.completedLesson) - } + fun onCompletedLessonsItemSelected(completedLesson: CompletedLesson) { + Timber.i("Select completed lessons item ${completedLesson.id}") + view?.showCompletedLessonDialog(completedLesson) } private fun setBaseDateOnHolidays() { - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getCurrentSemester(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } - private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading completed lessons data started") - currentDate = date - disposable.apply { - clear() - add(studentRepository.getCurrentStudent() - .flatMap { student -> - semesterRepository.getCurrentSemester(student).flatMap { semester -> - completedLessonsRepository.getCompletedLessons(student, semester, currentDate, currentDate, forceRefresh) - } - } - .delay(200, TimeUnit.MILLISECONDS) - .map { items -> items.map { CompletedLessonItem(it) } } - .map { items -> items.sortedBy { it.completedLesson.number } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Loading completed lessons lessons result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) - } - analytics.logEvent("load_completed_lessons", "items" to it.size, "force_refresh" to forceRefresh) - }) { - Timber.i("Loading completed lessons result: An exception occurred") - completedLessonsErrorHandler.dispatch(it) - }) + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + completedLessonsRepository.getCompletedLessons( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) } + .logResourceStatus("load completed lessons") + .mapResourceData { it.sortedBy { lesson -> lesson.number } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "completed_lessons", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -158,7 +155,9 @@ class CompletedLessonsPresenter @Inject constructor( } } - private fun reloadView() { + private fun reloadView(date: LocalDate) { + currentDate = date + Timber.i("Reload completed lessons view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) @@ -176,7 +175,7 @@ class CompletedLessonsPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays) - updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize()) + updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt index a6a327e2..715ce01f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.completed import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.ui.base.BaseView -import org.threeten.bp.LocalDate +import java.time.LocalDate interface CompletedLessonsView : BaseView { @@ -10,13 +10,13 @@ interface CompletedLessonsView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun clearData() fun updateNavigationDay(date: String) - fun hideRefresh() + fun showRefresh(show: Boolean) fun showEmpty(show: Boolean) @@ -38,5 +38,5 @@ interface CompletedLessonsView : BaseView { fun showCompletedLessonDialog(completedLesson: CompletedLesson) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 7636637f..672dbe72 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -1,71 +1,67 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.Intent import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG -import androidx.appcompat.app.AlertDialog -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.R +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_CONFIGURE import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER -import io.github.wulkanowy.utils.setOnItemClickListener -import kotlinx.android.synthetic.main.activity_widget_configure.* +import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject -class TimetableWidgetConfigureActivity : BaseActivity(), +@AndroidEntryPoint +class TimetableWidgetConfigureActivity : + BaseActivity(), TimetableWidgetConfigureView { @Inject - lateinit var configureAdapter: FlexibleAdapter> + lateinit var configureAdapter: WidgetConfigureAdapter @Inject override lateinit var presenter: TimetableWidgetConfigurePresenter - private var dialog: AlertDialog? = null + @Inject + lateinit var appInfo: AppInfo override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) - setContentView(R.layout.activity_widget_configure) + setContentView( + ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root + ) intent.extras.let { - presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID), it?.getBoolean(EXTRA_FROM_PROVIDER)) + presenter.onAttachView( + this, + it?.getInt(EXTRA_APPWIDGET_ID), + it?.getBoolean(EXTRA_FROM_PROVIDER) + ) } } override fun initView() { - with(widgetConfigureRecycler) { + with(binding.widgetConfigureRecycler) { adapter = configureAdapter - layoutManager = SmoothScrollLinearLayoutManager(context) + layoutManager = LinearLayoutManager(context) } - configureAdapter.setOnItemClickListener(presenter::onItemSelect) + configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - val items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - - override fun updateData(data: List) { - configureAdapter.updateDataSet(data) + override fun updateData(data: List, selectedStudentId: Long) { + with(configureAdapter) { + selectedId = selectedStudentId + items = data + notifyDataSetChanged() + } } override fun updateTimetableWidget(widgetId: Int) { @@ -73,6 +69,7 @@ class TimetableWidgetConfigureActivity : BaseActivity() { - - override fun getLayoutRes() = R.layout.item_account - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>) = ViewHolder(view, adapter) - - @SuppressLint("SetTextI18n") - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { - val context = holder.contentView.context - - val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) - else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153) - - with(holder) { - accountItemName.text = "${student.studentName} ${student.className}" - accountItemSchool.text = student.schoolName - accountItemImage.setColorFilter(colorImage) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TimetableWidgetConfigureItem - - if (student != other.student) return false - - return true - } - - override fun hashCode(): Int { - var result = student.hashCode() - result = 31 * result + student.id.toInt() - return result - } - - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), - LayoutContainer { - - override val containerView: View? get() = contentView - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index e1685108..87e89336 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -1,22 +1,22 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.SchedulersProvider +import kotlinx.coroutines.flow.onEach +import timber.log.Timber import javax.inject.Inject class TimetableWidgetConfigurePresenter @Inject constructor( - schedulers: SchedulersProvider, errorHandler: ErrorHandler, studentRepository: StudentRepository, private val sharedPref: SharedPrefProvider -) : BasePresenter(errorHandler, studentRepository, schedulers) { +) : BasePresenter(errorHandler, studentRepository) { private var appWidgetId: Int? = null @@ -24,7 +24,11 @@ class TimetableWidgetConfigurePresenter @Inject constructor( private var selectedStudent: Student? = null - fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) { + fun onAttachView( + view: TimetableWidgetConfigureView, + appWidgetId: Int?, + isFromProvider: Boolean? + ) { super.onAttachView(view) this.appWidgetId = appWidgetId this.isFromProvider = isFromProvider ?: false @@ -32,44 +36,28 @@ class TimetableWidgetConfigurePresenter @Inject constructor( loadData() } - fun onItemSelect(item: AbstractFlexibleItem<*>) { - if (item is TimetableWidgetConfigureItem) { - selectedStudent = item.student - - if (isFromProvider) registerStudent(selectedStudent) - else view?.showThemeDialog() - } - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } + fun onItemSelect(student: Student) { + selectedStudent = student registerStudent(selectedStudent) } - fun onDismissThemeView(){ - view?.finishView() - } - private fun loadData() { - disposable.add(studentRepository.getSavedStudents(false) - .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } - .map { (students, currentStudentId) -> - students.map { student -> TimetableWidgetConfigureItem(student, student.id == currentStudentId) } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - when { - it.isEmpty() -> view?.openLoginView() - it.size == 1 && !isFromProvider -> { - selectedStudent = it.single().student - view?.showThemeDialog() + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Timetable widget configure students data load") + is Resource.Success -> { + val selectedStudentId = appWidgetId?.let { id -> + sharedPref.getLong(getStudentWidgetKey(id), 0) + } ?: -1 + when { + it.data.isEmpty() -> view?.openLoginView() + it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student) + else -> view?.updateData(it.data, selectedStudentId) } - else -> view?.updateData(it) } - }, { errorHandler.dispatch(it) })) + is Resource.Error -> errorHandler.dispatch(it.error) + } + }.launch() } private fun registerStudent(student: Student?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt index 7cac892d..7740b9bb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt @@ -1,17 +1,16 @@ package io.github.wulkanowy.ui.modules.timetablewidget +import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView interface TimetableWidgetConfigureView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List, selectedStudentId: Long) fun updateTimetableWidget(widgetId: Int) - fun showThemeDialog() - fun setSuccessResult(widgetId: Int) fun finishView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index fb8c5958..e60d5488 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -1,63 +1,62 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import android.annotation.SuppressLint import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.graphics.Paint.ANTI_ALIAS_FLAG import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.view.View.GONE import android.view.View.VISIBLE -import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R +import io.github.wulkanowy.data.dataOrThrow import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS +import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.getErrorString +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString -import io.reactivex.Maybe -import org.threeten.bp.LocalDate +import kotlinx.coroutines.runBlocking import timber.log.Timber +import java.time.Instant +import java.time.LocalDate class TimetableWidgetFactory( private val timetableRepository: TimetableRepository, private val studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, - private val prefRepository: PreferencesRepository, private val sharedPref: SharedPrefProvider, - private val schedulers: SchedulersProvider, + private val prefRepository: PreferencesRepository, private val context: Context, private val intent: Intent? ) : RemoteViewsService.RemoteViewsFactory { - private var lessons = emptyList() - - private var savedTheme: Long? = null - - private var layoutId: Int? = null - - private var primaryColor: Int? = null - + private var items = emptyList() + private var timetableCanceledColor: Int? = null private var textColor: Int? = null - private var timetableChangeColor: Int? = null override fun getLoadingView() = null override fun hasStableIds() = true - override fun getCount() = lessons.size + override fun getCount() = items.size - override fun getViewTypeCount() = 2 + override fun getViewTypeCount() = 3 override fun getItemId(position: Int) = position.toLong() @@ -66,159 +65,267 @@ class TimetableWidgetFactory( override fun onDestroy() {} override fun onDataSetChanged() { - intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId -> - val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) - val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) + val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return + val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) + val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) - updateTheme(appWidgetId) + items = emptyList() - updateLessons(date, studentId) - } - } + runBlocking { + runCatching { + val student = getStudent(studentId) ?: return@runBlocking + val semester = semesterRepository.getCurrentSemester(student) + val lessons = getLessons(student, semester, date) + val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) - private fun updateTheme(appWidgetId: Int) { - savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - layoutId = if (savedTheme == 0L) R.layout.item_widget_timetable else R.layout.item_widget_timetable_dark - - primaryColor = if (savedTheme == 0L) R.color.colorPrimary else R.color.colorPrimaryLight - textColor = if (savedTheme == 0L) android.R.color.black else android.R.color.white - timetableChangeColor = if (savedTheme == 0L) R.color.timetable_change_dark else R.color.timetable_change_light - } - - private fun getItemLayout(lesson: Timetable): Int { - return when { - prefRepository.showWholeClassPlan == "small" && !lesson.isStudentPlan -> { - if (savedTheme == 0L) R.layout.item_widget_timetable_small - else R.layout.item_widget_timetable_small_dark + createItems(lessons, lastSync, !(student.isEduOne ?: false)) } - savedTheme == 0L -> R.layout.item_widget_timetable - else -> R.layout.item_widget_timetable_dark - } - } - - private fun updateLessons(date: LocalDate, studentId: Long) { - lessons = try { - studentRepository.isStudentSaved() - .filter { true } - .flatMap { studentRepository.getSavedStudents().toMaybe() } - .flatMap { - val student = it.singleOrNull { student -> student.id == studentId } - - if (student != null) Maybe.just(student) - else Maybe.empty() + .onFailure { + items = listOf(TimetableWidgetItem.Error(it)) + Timber.e(it, "An error has occurred in timetable widget factory") } - .flatMap { student -> - semesterRepository.getCurrentSemester(student).toMaybe().flatMap { semester -> - timetableRepository.getTimetable(student, semester, date, date).toMaybe() + .onSuccess { + items = it + if (date == LocalDate.now()) { + updateTodayLastLessonEnd(appWidgetId) } } - .map { items -> items.sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) } - .map { lessons -> lessons.filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } } - .subscribeOn(schedulers.backgroundThread) - .blockingGet(emptyList()) - } catch (e: Exception) { - Timber.e(e, "An error has occurred in timetable widget factory") - emptyList() } } - @SuppressLint("DefaultLocale") - override fun getViewAt(position: Int): RemoteViews? { - if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null + private suspend fun getStudent(studentId: Long): Student? { + val students = studentRepository.getSavedStudents() + return students.singleOrNull { it.student.id == studentId }?.student + } - val lesson = lessons[position] - return RemoteViews(context.packageName, getItemLayout(lesson)).apply { - setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) - setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) - setTextViewText(R.id.timetableWidgetItemTimeStart, lesson.start.toFormattedString("HH:mm")) - setTextViewText(R.id.timetableWidgetItemTimeFinish, lesson.end.toFormattedString("HH:mm")) + private suspend fun getLessons( + student: Student, semester: Semester, date: LocalDate + ): List { + val timetable = timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = false, + isFromAppWidget = true + ) + val lessons = timetable.toFirstResult().dataOrThrow.lessons + return lessons.sortedBy { it.start } + } - updateDescription(this, lesson) + private fun createItems( + lessons: List, + lastSync: Instant?, + isEduOne: Boolean + ): List { + var prevNum = when (prefRepository.showTimetableGaps) { + BETWEEN_AND_BEFORE_LESSONS -> 0 + else -> null + } - if (lesson.canceled) { - updateStylesCanceled(this) - } else { - updateStylesNotCanceled(this, lesson) + return buildList { + lessons.forEach { + if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) { + val emptyItem = TimetableWidgetItem.Empty( + numFrom = prevNum!! + 1, + numTo = it.number - 1 + ) + add(emptyItem) + } + add(TimetableWidgetItem.Normal(it, isEduOne)) + prevNum = it.number } + add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN)) + } + } + private fun updateTodayLastLessonEnd(appWidgetId: Int) { + val todayLastLessonEnd = items.filterIsInstance() + .maxOfOrNull { it.lesson.end } ?: return + val key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId) + sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) + } + + override fun getViewAt(position: Int): RemoteViews? { + return when (val item = items.getOrNull(position) ?: return null) { + is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item) + is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item) + is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item) + is TimetableWidgetItem.Error -> getErrorItemRemoteView(item) + } + } + + private fun getNormalItemRemoteView(item: TimetableWidgetItem.Normal): RemoteViews { + val lesson = item.lesson + + val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE) + val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE) + val lessonNumberVisibility = if (item.isLessonNumberVisible) VISIBLE else GONE + + val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { + setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) + setViewVisibility(R.id.timetableWidgetItemNumber, lessonNumberVisibility) + setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime) + setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime) + setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) + + setTextViewText(R.id.timetableWidgetItemTeacher, lesson.teacher) + setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent()) } + updateTheme() + clearLessonStyles(remoteViews) + if (lesson.room.isBlank()) { + remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + } else { + remoteViews.setTextViewText(R.id.timetableWidgetItemRoom, lesson.room) + } + when { + lesson.canceled -> applyCancelledLessonStyles(remoteViews) + lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles( + remoteViews = remoteViews, + lesson = lesson, + ) + } + return remoteViews } - private fun updateDescription(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { - if (lesson.info.isNotBlank() && !lesson.changes) { - setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) - setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemRoom, GONE) - setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) - } else { - setViewVisibility(R.id.timetableWidgetItemDescription, GONE) - setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + private fun getEmptyItemRemoteView(item: TimetableWidgetItem.Empty): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_empty + ).apply { + setTextViewText( + R.id.timetableWidgetEmptyItemNumber, + when (item.numFrom) { + item.numTo -> item.numFrom.toString() + else -> "${item.numFrom}-${item.numTo}" + } + ) + setTextViewText( + R.id.timetableWidgetEmptyItemText, + context.getPlural( + R.plurals.timetable_no_lesson, + item.numTo - item.numFrom + 1 + ) + ) + setOnClickFillInIntent(R.id.timetableWidgetEmptyItemContainer, Intent()) + } + } + + private fun getSynchronizedItemRemoteView(item: TimetableWidgetItem.Synchronized): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_footer + ).apply { + setTextViewText( + R.id.timetableWidgetSynchronizationTime, + getSynchronizationInfoText(item.timestamp) + ) + } + } + + private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_error + ).apply { + setTextViewText( + R.id.timetable_widget_item_error_message, + context.resources.getErrorString(item.error) + ) + } + } + + private fun updateTheme() { + when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> { + textColor = android.R.color.white + timetableChangeColor = R.color.timetable_change_dark + timetableCanceledColor = R.color.timetable_canceled_dark + } + + else -> { + textColor = android.R.color.black + timetableChangeColor = R.color.timetable_change_light + timetableCanceledColor = R.color.timetable_canceled_light } } } - private fun updateStylesCanceled(remoteViews: RemoteViews) { - with(remoteViews) { - setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", - STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG) - setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!)) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!)) - setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(primaryColor!!)) - } - } + private fun clearLessonStyles(remoteViews: RemoteViews) { + val defaultTextColor = context.getCompatColor(textColor ?: 0) - private fun updateStylesNotCanceled(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { + remoteViews.apply { setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!)) - setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(timetableChangeColor!!)) - - updateNotCanceledLessonNumberColor(this, lesson) - updateNotCanceledSubjectColor(this, lesson) - - val teacherChange = lesson.teacherOld.isNotBlank() && lesson.teacher != lesson.teacherOld - updateNotCanceledRoom(this, lesson, teacherChange) - updateNotCanceledTeacher(this, lesson, teacherChange) + setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemIcon, GONE) + setViewVisibility(R.id.timetableWidgetItemDescription, GONE) + setTextColor(R.id.timetableWidgetItemNumber, defaultTextColor) + setTextColor(R.id.timetableWidgetItemSubject, defaultTextColor) + setTextColor(R.id.timetableWidgetItemRoom, defaultTextColor) + setTextColor(R.id.timetableWidgetItemTeacher, defaultTextColor) + setTextColor(R.id.timetableWidgetItemDescription, defaultTextColor) } } - private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor( - if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!! - else textColor!! - )) - } + private fun applyCancelledLessonStyles(remoteViews: RemoteViews) { + val cancelledThemeColor = context.getCompatColor(timetableCanceledColor ?: 0) + val strikeThroughPaintFlags = STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG - private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor( - if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!! - else textColor!! - )) - } - - private fun updateNotCanceledRoom(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) { - with(remoteViews) { - if (lesson.room.isNotBlank()) { - setTextViewText(R.id.timetableWidgetItemRoom, - if (teacherChange) lesson.room - else "${context.getString(R.string.timetable_room)} ${lesson.room}" - ) - - setTextColor(R.id.timetableWidgetItemRoom, context.getCompatColor( - if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!! - else textColor!! - )) - } else setTextViewText(R.id.timetableWidgetItemRoom, "") + remoteViews.apply { + setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", strikeThroughPaintFlags) + setTextColor(R.id.timetableWidgetItemNumber, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemSubject, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemDescription, cancelledThemeColor) + setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) } } - private fun updateNotCanceledTeacher(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) { - remoteViews.setTextViewText(R.id.timetableWidgetItemTeacher, - if (teacherChange) lesson.teacher - else "" - ) + private fun applyChangedLessonStyles(remoteViews: RemoteViews, lesson: Timetable) { + val changesTextColor = context.getCompatColor(timetableChangeColor ?: 0) + + remoteViews.apply { + setTextColor(R.id.timetableWidgetItemNumber, changesTextColor) + setTextColor(R.id.timetableWidgetItemDescription, changesTextColor) + setViewVisibility(R.id.timetableWidgetItemIcon, VISIBLE) + setImageViewResource(R.id.timetableWidgetItemIcon, R.drawable.ic_timetable_widget_swap) + } + + if (lesson.subject != lesson.subjectOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemSubject, changesTextColor) + } + + if (lesson.room != lesson.roomOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemRoom, changesTextColor) + } + + if (lesson.teacher != lesson.teacherOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemTeacher, changesTextColor) + } + + if (lesson.info.isNotBlank() && !lesson.changes) { + remoteViews.setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) + } + } + + private fun getSynchronizationInfoText(synchronizationInstant: Instant) = + synchronizationInstant.run { + val synchronizationTime = toFormattedString(TIME_FORMAT_STYLE) + val synchronizationDate = toFormattedString() + context.getString( + R.string.widget_timetable_last_synchronization, + synchronizationDate, + synchronizationTime, + ) + } + + private companion object { + private const val TIME_FORMAT_STYLE = "HH:mm" } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt new file mode 100644 index 00000000..d4c2cfc0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.modules.timetablewidget + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.Instant + +sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) { + + data class Normal( + val lesson: Timetable, + val isLessonNumberVisible: Boolean, + ) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL) + + data class Empty( + val numFrom: Int, + val numTo: Int + ) : TimetableWidgetItem(TimetableWidgetItemType.EMPTY) + + data class Synchronized( + val timestamp: Instant, + ) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED) + + data class Error( + val error: Throwable + ) : TimetableWidgetItem(TimetableWidgetItemType.ERROR) +} + +enum class TimetableWidgetItemType { + NORMAL, + EMPTY, + SYNCHRONIZED, + ERROR, +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 62192a1b..cc48539a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -1,40 +1,38 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import android.annotation.SuppressLint import android.app.PendingIntent -import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.graphics.Bitmap import android.widget.RemoteViews -import dagger.android.AndroidInjection +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.toBitmap +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.widgets.TimetableWidgetService -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper -import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString -import io.reactivex.Maybe -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDate.now +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.splash.SplashActivity +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZoneOffset import javax.inject.Inject +@AndroidEntryPoint class TimetableWidgetProvider : BroadcastReceiver() { @Inject @@ -47,13 +45,12 @@ class TimetableWidgetProvider : BroadcastReceiver() { lateinit var sharedPref: SharedPrefProvider @Inject - lateinit var schedulers: SchedulersProvider - - @Inject - lateinit var analytics: FirebaseAnalyticsHelper + lateinit var analytics: AnalyticsHelper companion object { + private const val TIMETABLE_PENDING_INTENT_ID = 201 + private const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget" private const val EXTRA_BUTTON_TYPE = "extraButtonType" @@ -64,133 +61,265 @@ class TimetableWidgetProvider : BroadcastReceiver() { private const val BUTTON_RESET = "buttonReset" + const val EXTRA_FROM_CONFIGURE = "extraFromConfigure" + const val EXTRA_FROM_PROVIDER = "extraFromProvider" fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId" + fun getTodayLastLessonEndDateTimeWidgetKey(appWidgetId: Int) = + "timetable_widget_today_last_lesson_end_date_time_$appWidgetId" + fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" } + @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { - AndroidInjection.inject(this, context) - when (intent.action) { - ACTION_APPWIDGET_UPDATE -> onUpdate(context, intent) - ACTION_APPWIDGET_DELETED -> onDelete(intent) + GlobalScope.launch { + when (intent.action) { + ACTION_APPWIDGET_UPDATE -> onWidgetUpdate(context, intent) + ACTION_APPWIDGET_DELETED -> onWidgetDeleted(intent) + } } } - private fun onUpdate(context: Context, intent: Intent) { - if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) { - intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> - val student = getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student) - } + private suspend fun onWidgetUpdate(context: Context, intent: Intent) { + val pressedButton = intent.getPressedButton() + + if (pressedButton == null) { + val updatedWidgetIds = intent.getWidgetIds() ?: return + updatedWidgetIds.forEach { updateWidgetLayout(context, it) } } else { - val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) - val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) - val student = getStudent(sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId) - val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) - val date = when (buttonType) { - BUTTON_RESET -> now().nextOrSameSchoolDay - BUTTON_NEXT -> savedDate.nextSchoolDay - BUTTON_PREV -> savedDate.previousSchoolDay - else -> now().nextOrSameSchoolDay - } - if (!buttonType.isNullOrBlank()) analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) - updateWidget(context, toggledWidgetId, date, student) + val widgetId = intent.getToggledWidgetId() ?: return + reportChangedDay(pressedButton) + updateSavedWidgetDate(widgetId, pressedButton) + updateWidgetLayout(context, widgetId) } } - private fun onDelete(intent: Intent) { - val appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0) + private fun Intent.getPressedButton(): String? { + return getStringExtra(EXTRA_BUTTON_TYPE) + } - if (appWidgetId != 0) { - with(sharedPref) { - delete(getStudentWidgetKey(appWidgetId)) - delete(getDateWidgetKey(appWidgetId)) - } + private fun Intent.getWidgetIds(): IntArray? { + return getIntArrayExtra(EXTRA_APPWIDGET_IDS) + } + + private fun Intent.getToggledWidgetId(): Int? { + val toggledWidgetId = getIntExtra(EXTRA_TOGGLED_WIDGET_ID, INVALID_APPWIDGET_ID) + return toggledWidgetId.takeIf { it != INVALID_APPWIDGET_ID } + } + + private fun reportChangedDay(buttonType: String) { + if (buttonType.isNotBlank()) { + analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) } } - @SuppressLint("DefaultLocale") - private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val layoutId = if (savedTheme == 0L) R.layout.widget_timetable else R.layout.widget_timetable_dark + private fun updateSavedWidgetDate(widgetId: Int, buttonType: String) { + val savedDate = getSavedWidgetDate(widgetId) + val newDate = savedDate?.let { getNewDate(it, widgetId, buttonType) } + ?: getWidgetDefaultDateToLoad(widgetId) + setWidgetDate(widgetId, newDate) + } - val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) - val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) - val resetNavIntent = createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) - val adapterIntent = Intent(context, TimetableWidgetService::class.java) - .apply { - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - //make Intent unique - action = appWidgetId.toString() - } - val accountIntent = PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId, - Intent(context, TimetableWidgetConfigureActivity::class.java).apply { - addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - putExtra(EXTRA_FROM_PROVIDER, true) - }, FLAG_UPDATE_CURRENT) - val appIntent = PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id, - MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT) + private fun getSavedWidgetDate(widgetId: Int): LocalDate? { + val epochDay = sharedPref.getLong(getDateWidgetKey(widgetId), 0) + return if (epochDay == 0L) null else LocalDate.ofEpochDay(epochDay) + } - val remoteView = RemoteViews(context.packageName, layoutId).apply { - setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) - setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize()) - setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data)) - setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) + private fun getNewDate( + currentDate: LocalDate, + widgetId: Int, + selectedButton: String + ): LocalDate { + return when (selectedButton) { + BUTTON_NEXT -> currentDate.nextSchoolDay + BUTTON_PREV -> currentDate.previousSchoolDay + else -> getWidgetDefaultDateToLoad(widgetId) + } + } + + private fun setWidgetDate(widgetId: Int, dateToSet: LocalDate) { + val widgetDateKey = getDateWidgetKey(widgetId) + sharedPref.putLong(widgetDateKey, dateToSet.toEpochDay(), true) + } + + private fun getWidgetDefaultDateToLoad(widgetId: Int): LocalDate { + val lastLessonEndDateTime = getLastLessonDateTime(widgetId) + + val todayDate = LocalDate.now() + val isLastLessonToday = lastLessonEndDateTime.toLocalDate() == todayDate + val isEndOfLessons = LocalDateTime.now() > lastLessonEndDateTime + + return if (isLastLessonToday && isEndOfLessons) { + todayDate.nextSchoolDay + } else { + todayDate.nextOrSameSchoolDay + } + } + + private fun getLastLessonDateTime(widgetId: Int): LocalDateTime { + val lastLessonTimestamp = sharedPref + .getLong(getTodayLastLessonEndDateTimeWidgetKey(widgetId), 0) + return LocalDateTime.ofEpochSecond(lastLessonTimestamp, 0, ZoneOffset.UTC) + } + + private suspend fun updateWidgetLayout( + context: Context, widgetId: Int + ) { + val widgetRemoteViews = RemoteViews(context.packageName, R.layout.widget_timetable) + + // Apply the click action intent + val appIntent = createPendingAppIntent(context) + widgetRemoteViews.setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) + + // Display saved date + val date = getSavedWidgetDate(widgetId) ?: getWidgetDefaultDateToLoad(widgetId) + val formattedDate = date.toFormattedString("EEE, dd.MM").capitalise() + widgetRemoteViews.setTextViewText(R.id.timetableWidgetDate, formattedDate) + + // Apply intents to the date switcher buttons + val nextNavIntent = createNavButtonIntent(context, widgetId, widgetId, BUTTON_NEXT) + val prevNavIntent = createNavButtonIntent(context, -widgetId, widgetId, BUTTON_PREV) + val resetNavIntent = + createNavButtonIntent(context, Int.MAX_VALUE - widgetId, widgetId, BUTTON_RESET) + widgetRemoteViews.run { setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetName, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetAccount, accountIntent) - setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) } - sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) + // Setup the lesson list adapter + val lessonListAdapterIntent = createLessonListAdapterIntent(context, widgetId) + // --- Ensure the selected date is stored in the shared preferences, + // --- on which the TimetableWidgetFactory relies + setWidgetDate(widgetId, date) + // --- + widgetRemoteViews.apply { + setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) + setRemoteAdapter(R.id.timetableWidgetList, lessonListAdapterIntent) + } + + // Setup profile picture + getWidgetStudent(widgetId)?.let { student -> + setupAccountView(context, student, widgetRemoteViews, widgetId) + } + + // Apply updates with(appWidgetManager) { - notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) - updateAppWidget(appWidgetId, remoteView) + partiallyUpdateAppWidget(widgetId, widgetRemoteViews) + notifyAppWidgetViewDataChanged(widgetId, R.id.timetableWidgetList) + } + + Timber.d("TimetableWidgetProvider updated") + } + + private fun createPendingAppIntent(context: Context) = PendingIntent.getActivity( + context, TIMETABLE_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.Timetable()), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + private fun createNavButtonIntent( + context: Context, code: Int, appWidgetId: Int, buttonType: String + ) = PendingIntent.getBroadcast( + context, code, Intent(context, TimetableWidgetProvider::class.java).apply { + action = ACTION_APPWIDGET_UPDATE + putExtra(EXTRA_BUTTON_TYPE, buttonType) + putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) + }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + private fun createLessonListAdapterIntent(context: Context, widgetId: Int) = + Intent(context, TimetableWidgetService::class.java).apply { + putExtra(EXTRA_APPWIDGET_ID, widgetId) + action = widgetId.toString() //make Intent unique + } + + private suspend fun getWidgetStudent(widgetId: Int): Student? { + val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) + return getStudent(studentId, widgetId) + } + + private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { + val students = studentRepository.getSavedStudents(false) + val student = students.singleOrNull { it.student.id == studentId }?.student + when { + student != null -> student + studentId != 0L && studentRepository.isCurrentStudentSet() -> { + studentRepository.getCurrentStudent(false).also { + sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) + } + } + + else -> null + } + } catch (e: Exception) { + if (e.cause !is NoCurrentStudentException) { + Timber.e(e, "An error has occurred in timetable widget provider") + } + null + } + + private fun setupAccountView( + context: Context, student: Student, remoteViews: RemoteViews, widgetId: Int + ) { + val accountInitials = getAccountInitials(student.nickOrName) + val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId) + + getAvatarBackgroundBitmap(context, student.avatarColor)?.let { + remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, it) + } + + remoteViews.apply { + setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials) + setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerPendingIntent) } } - private fun createNavIntent(context: Context, code: Int, appWidgetId: Int, buttonType: String): PendingIntent { - return PendingIntent.getBroadcast(context, code, - Intent(context, TimetableWidgetProvider::class.java).apply { - action = ACTION_APPWIDGET_UPDATE - putExtra(EXTRA_BUTTON_TYPE, buttonType) - putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) - }, FLAG_UPDATE_CURRENT) + private fun getAccountInitials(name: String): String { + val firstLetters = name.split(" ").mapNotNull { it.firstOrNull() } + return firstLetters.joinToString(separator = "").uppercase() } - private fun getStudent(studentId: Long, appWidgetId: Int): Student? { - return try { - studentRepository.isStudentSaved() - .filter { true } - .flatMap { studentRepository.getSavedStudents(false).toMaybe() } - .flatMap { students -> - val student = students.singleOrNull { student -> student.id == studentId } - when { - student != null -> Maybe.just(student) - studentId != 0L -> { - studentRepository.isCurrentStudentSet() - .filter { true } - .flatMap { studentRepository.getCurrentStudent(false).toMaybe() } - .doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) } - } - else -> Maybe.empty() - } - } - .subscribeOn(schedulers.backgroundThread) - .blockingGet() - } catch (e: Exception) { - if (e.cause !is NoCurrentStudentException) { - Timber.e(e, "An error has occurred in timetable widget provider") + private fun createAccountPickerPendingIntent(context: Context, widgetId: Int) = + PendingIntent.getActivity( + context, + -Int.MAX_VALUE + widgetId, + Intent(context, TimetableWidgetConfigureActivity::class.java).apply { + addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) + putExtra(EXTRA_APPWIDGET_ID, widgetId) + putExtra(EXTRA_FROM_PROVIDER, true) + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + private fun getAvatarBackgroundBitmap(context: Context, avatarColor: Long): Bitmap? { + val avatarDrawableResource = R.drawable.background_timetable_widget_avatar + return AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable -> + val screenDensity = context.resources.displayMetrics.density + val avatarSize = (48 * screenDensity).toInt() + DrawableCompat.wrap(drawable).run { + DrawableCompat.setTint(this, avatarColor.toInt()) + toBitmap(avatarSize, avatarSize) } - null + } + } + + private fun onWidgetDeleted(intent: Intent) { + val deletedWidgetId = intent.getWidgetId() + deleteWidgetPreferences(deletedWidgetId) + } + + private fun Intent.getWidgetId(): Int { + return getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID) + } + + private fun deleteWidgetPreferences(widgetId: Int) { + with(sharedPref) { + delete(getStudentWidgetKey(widgetId)) + delete(getDateWidgetKey(widgetId)) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt new file mode 100644 index 00000000..93330131 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/DividerItemDecoration.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.widgets + +import android.content.Context +import android.graphics.Canvas +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView + +class DividerItemDecoration( + context: Context, + private val showDividerWithFirstItem: Boolean = true +) : DividerItemDecoration(context, VERTICAL) { + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + canvas.save() + val dividerLeft = parent.paddingLeft + val dividerRight = parent.width - parent.paddingRight + + for (i in 0..parent.childCount - 2) { + if (!showDividerWithFirstItem && i == 0) continue + + val child = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val dividerTop = child.bottom + params.bottomMargin + val dividerBottom = dividerTop + drawable!!.intrinsicHeight + + drawable?.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom) + drawable?.draw(canvas) + } + canvas.restore() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt index 0f121dc5..6b7fb4aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt @@ -3,17 +3,15 @@ package io.github.wulkanowy.ui.widgets import android.content.Context import android.util.AttributeSet import android.view.ViewGroup +import com.google.android.material.tabs.TabLayout /** * @see Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout */ -class FittedScrollableTabLayout : MaterialTabLayout { - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) +class FittedScrollableTabLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : TabLayout(context, attrs) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { setMeasuredDimension(widthMeasureSpec, heightMeasureSpec) diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt index a04922e5..4e1ca1a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt @@ -1,24 +1,19 @@ package io.github.wulkanowy.ui.widgets import android.content.Context -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP import android.util.AttributeSet import android.widget.LinearLayout import androidx.core.view.ViewCompat -import com.google.android.material.elevation.ElevationOverlayProvider import com.google.android.material.shape.MaterialShapeDrawable -class MaterialLinearLayout : LinearLayout { - - constructor(context: Context) : super(context) - - constructor(context: Context, attr: AttributeSet) : super(context, attr) - - constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr) +class MaterialLinearLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : LinearLayout(context, attrs) { init { - val drawable = MaterialShapeDrawable.createWithElevationOverlay(context, ViewCompat.getElevation(this)) + val drawable = + MaterialShapeDrawable.createWithElevationOverlay(context, ViewCompat.getElevation(this)) ViewCompat.setBackground(this, drawable) } @@ -28,12 +23,4 @@ class MaterialLinearLayout : LinearLayout { (background as MaterialShapeDrawable).elevation = elevation } } - - fun setElevationCompat(elevation: Float) { - if (SDK_INT >= LOLLIPOP) { - setElevation(elevation) - } else { - setBackgroundColor(ElevationOverlayProvider(context).compositeOverlayWithThemeSurfaceColorIfNeeded(elevation)) - } - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt deleted file mode 100644 index e19d0111..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.ui.widgets - -import android.content.Context -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.LOLLIPOP -import android.util.AttributeSet -import com.google.android.material.elevation.ElevationOverlayProvider -import com.google.android.material.tabs.TabLayout - -open class MaterialTabLayout : TabLayout { - - constructor(context: Context) : super(context) - - constructor(context: Context, attr: AttributeSet) : super(context, attr) - - constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr) - - fun setElevationCompat(elevation: Float) { - if (SDK_INT >= LOLLIPOP) { - setElevation(elevation) - } else { - setBackgroundColor(ElevationOverlayProvider(context).compositeOverlayWithThemeSurfaceColorIfNeeded(elevation)) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt index a444da6f..e16db53c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt @@ -1,33 +1,39 @@ package io.github.wulkanowy.utils import android.content.res.Resources -import android.os.Build.MANUFACTURER -import android.os.Build.MODEL -import android.os.Build.VERSION.SDK_INT -import io.github.wulkanowy.BuildConfig.CRASHLYTICS_ENABLED -import io.github.wulkanowy.BuildConfig.DEBUG -import io.github.wulkanowy.BuildConfig.VERSION_CODE -import io.github.wulkanowy.BuildConfig.VERSION_NAME +import android.os.Build +import io.github.wulkanowy.BuildConfig +import javax.inject.Inject import javax.inject.Singleton @Singleton -open class AppInfo { +open class AppInfo @Inject constructor() { - open val isCrashlyticsEnabled get() = CRASHLYTICS_ENABLED + open val isDebug get() = BuildConfig.DEBUG - open val isDebug get() = DEBUG + open val versionCode get() = BuildConfig.VERSION_CODE - open val versionCode get() = VERSION_CODE + open val buildTimestamp get() = BuildConfig.BUILD_TIMESTAMP - open val versionName get() = VERSION_NAME + open val buildFlavor get() = BuildConfig.FLAVOR - open val systemVersion get() = SDK_INT + open val versionName get() = BuildConfig.VERSION_NAME - open val systemManufacturer: String get() = MANUFACTURER + open val systemVersion get() = Build.VERSION.SDK_INT - open val systemModel: String get() = MODEL + open val systemManufacturer: String get() = Build.MANUFACTURER + + open val systemModel: String get() = Build.MODEL + + open val messagesBaseUrl: String = BuildConfig.MESSAGES_BASE_URL + open val schoolsBaseUrl: String = BuildConfig.SCHOOLS_BASE_URL @Suppress("DEPRECATION") open val systemLanguage: String get() = Resources.getSystem().configuration.locale.language + + val defaultColorsForAvatar = listOf( + 0xd32f2f, 0xE64A19, 0xFFA000, 0xAFB42B, 0x689F38, 0x388E3C, 0x00796B, 0x0097A7, + 0x1976D2, 0x3647b5, 0x6236c9, 0x9225c1, 0xC2185B, 0x616161, 0x455A64, 0x7a5348 + ).map { (it and 0x00ffffff or (255 shl 24)).toLong() } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt new file mode 100644 index 00000000..1b54f40c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.utils + +import android.appwidget.AppWidgetManager +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber +import javax.inject.Inject +import kotlin.reflect.KClass + +class AppWidgetUpdater @Inject constructor( + @ApplicationContext private val context: Context, + private val appWidgetManager: AppWidgetManager +) { + + fun updateAllAppWidgetsByProvider(providerClass: KClass) { + try { + val ids = appWidgetManager.getAppWidgetIds(ComponentName(context, providerClass.java)) + if (ids.isEmpty()) return + + val intent = Intent(context, providerClass.java) + .apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) + } + + context.sendBroadcast(intent) + } catch (e: Exception) { + Timber.e(e, "Failed to update all widgets for provider $providerClass") + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt index c2b1efaa..3cac0b48 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt @@ -1,26 +1,43 @@ package io.github.wulkanowy.utils +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory /** * [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji] * (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf) */ -private inline val AttendanceSummary.allPresences: Double - get() = presence.toDouble() + absenceForSchoolReasons + lateness + latenessExcused +inline val AttendanceSummary.allPresences: Int + get() = presence + absenceForSchoolReasons + lateness + latenessExcused -private inline val AttendanceSummary.allAbsences: Double - get() = absence.toDouble() + absenceExcused +inline val AttendanceSummary.allAbsences: Int + get() = absence + absenceExcused -fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences) +inline val Attendance.isExcusableOrNotExcused: Boolean + get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null + +fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences.toDouble(), allAbsences.toDouble()) fun List.calculatePercentage(): Double { - return calculatePercentage(sumByDouble { it.allPresences }, sumByDouble { it.allAbsences }) + return calculatePercentage(sumOf { it.allPresences.toDouble() }, sumOf { it.allAbsences.toDouble() }) } private fun calculatePercentage(presence: Double, absence: Double): Double { return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100 } - +inline val Attendance.descriptionRes + get() = when (AttendanceCategory.getCategoryByName(name)) { + AttendanceCategory.PRESENCE -> R.string.attendance_present + AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused + AttendanceCategory.ABSENCE_EXCUSED -> R.string.attendance_absence_excused + AttendanceCategory.UNEXCUSED_LATENESS -> R.string.attendance_unexcused_lateness + AttendanceCategory.EXCUSED_LATENESS -> R.string.attendance_excused_lateness + AttendanceCategory.ABSENCE_FOR_SCHOOL_REASONS -> R.string.attendance_absence_school + AttendanceCategory.EXEMPTION -> R.string.attendance_exemption + AttendanceCategory.DELETED -> R.string.attendance_deleted + else -> R.string.attendance_unknown + } diff --git a/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt new file mode 100644 index 00000000..002612a8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.utils + +abstract class BaseRemoteConfigHelper { + + open fun initialize() = Unit + + open val userAgentTemplate: String + get() = RemoteConfigDefaults.USER_AGENT_TEMPLATE.value as String +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt new file mode 100644 index 00000000..b1742b4f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.utils + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Parcelable +import androidx.core.os.BundleCompat +import java.io.Serializable + +// Even though API was introduced in 33, we use 34 as 33 is bugged in some scenarios. + +inline fun Bundle.serializable(key: String): T = when { + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java)!! + else -> @Suppress("DEPRECATION") getSerializable(key) as T +} + +inline fun Bundle.nullableSerializable(key: String): T? = when { + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializable(key) as T? +} + +@Suppress("UNCHECKED_CAST") +inline fun Bundle.parcelableArray(key: String): Array? = + BundleCompat.getParcelableArray(this, key, T::class.java) as Array? + +inline fun Intent.serializable(key: String): T = when { + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java)!! + else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T +} + +inline fun Intent.nullableSerializable(key: String): T? = when { + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T? +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 582b1c83..77f3eb64 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -1,14 +1,19 @@ package io.github.wulkanowy.utils +import android.annotation.SuppressLint import android.content.Context -import android.content.Intent -import android.net.Uri +import android.content.res.ColorStateList +import android.graphics.* +import android.text.TextPaint import android.util.DisplayMetrics.DENSITY_DEFAULT -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes +import androidx.annotation.* import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.drawable.RoundedBitmapDrawable +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import androidx.core.graphics.drawable.toBitmap + @ColorInt fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { @@ -20,42 +25,68 @@ fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { } } +@ColorInt +fun Context.getThemeAttrColor(@AttrRes colorAttr: Int, alpha: Int): Int { + return ColorUtils.setAlphaComponent(getThemeAttrColor(colorAttr), alpha) +} + @ColorInt fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes) -fun Context.getCompatDrawable(@DrawableRes drawableRes: Int) = ContextCompat.getDrawable(this, drawableRes) +fun Context.getCompatDrawable(@DrawableRes drawableRes: Int) = + ContextCompat.getDrawable(this, drawableRes) -fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) { - Intent.parseUri(uri, 0).let { - if (it.resolveActivity(packageManager) != null) startActivity(it) - else onActivityNotFound(uri) - } -} - -fun Context.openEmailClient(chooserTitle: String, email: String, subject: String, body: String, onActivityNotFound: () -> Unit = {}) { - val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply { - putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) - putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, body) +fun Context.getCompatDrawable(@DrawableRes drawableRes: Int, @ColorRes colorRes: Int) = + getCompatDrawable(drawableRes)?.mutate()?.apply { + colorFilter = PorterDuffColorFilter( + getCompatColor(colorRes), PorterDuff.Mode.MULTIPLY + ) } - if (intent.resolveActivity(packageManager) != null) { - startActivity(Intent.createChooser(intent, chooserTitle)) - } else onActivityNotFound() -} +fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: Int) = + getCompatDrawable(drawableRes, colorRes)?.toBitmap() -fun Context.openNavigation(location: String) { - val intentUri = Uri.parse("geo:0,0?q=${Uri.encode(location)}") - val intent = Intent(Intent.ACTION_VIEW, intentUri) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } -} - -fun Context.openDialer(phone: String) { - val intentUri = Uri.parse("tel:$phone") - val intent = Intent(Intent.ACTION_DIAL, intentUri) - startActivity(intent) -} +fun Context.getPlural(@PluralsRes pluralRes: Int, quantity: Int, vararg arguments: Any) = + resources.getQuantityString(pluralRes, quantity, *arguments) fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT + +@SuppressLint("DefaultLocale") +fun Context.createNameInitialsDrawable( + text: String, + backgroundColor: Long, + scaleFactory: Float = 1f +): RoundedBitmapDrawable { + val words = text.split(" ") + val firstCharFirstWord = words.getOrNull(0)?.firstOrNull() ?: "" + val firstCharSecondWord = words.getOrNull(1)?.firstOrNull() ?: "" + + val initials = "$firstCharFirstWord$firstCharSecondWord".uppercase() + + val bounds = Rect() + val dimension = this.dpToPx(64f * scaleFactory).toInt() + val textPaint = TextPaint().apply { + typeface = Typeface.SANS_SERIF + color = Color.WHITE + textAlign = Paint.Align.CENTER + isAntiAlias = true + textSize = this@createNameInitialsDrawable.dpToPx(30f * scaleFactory) + getTextBounds(initials, 0, initials.length, bounds) + } + + val xCoordinate = (dimension / 2).toFloat() + val yCoordinate = (dimension / 2 + (bounds.bottom - bounds.top) / 2).toFloat() + + val bitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888) + .applyCanvas { + drawColor(backgroundColor.toInt()) + drawText(initials, 0, initials.length, xCoordinate, yCoordinate, textPaint) + } + + return RoundedBitmapDrawableFactory.create(this.resources, bitmap) + .apply { isCircular = true } +} + +fun Context.getAttrColorStateList(@AttrRes color: Int): ColorStateList { + return ColorStateList.valueOf(getThemeAttrColor(color)) +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt b/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt new file mode 100644 index 00000000..8aaa57f4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.utils + +import kotlinx.coroutines.Dispatchers + +open class DispatchersProvider { + + open val io get() = Dispatchers.IO +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt new file mode 100644 index 00000000..d541c0a7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -0,0 +1,82 @@ +package io.github.wulkanowy.utils + +import android.content.res.Resources +import io.github.wulkanowy.R +import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.AccountInactiveException +import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException +import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException +import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException +import io.github.wulkanowy.sdk.scrapper.exception.VulcanException +import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException +import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException +import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException +import okhttp3.internal.http2.StreamResetException +import java.io.InterruptedIOException +import java.net.ConnectException +import java.net.SocketException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.security.cert.CertPathValidatorException +import java.security.cert.CertificateExpiredException +import java.security.cert.CertificateNotYetValidException +import javax.net.ssl.SSLHandshakeException + +fun Resources.getErrorString(error: Throwable): String = when (error) { + is UnknownHostException -> R.string.error_no_internet + is ConnectException, + is SocketException, + is SocketTimeoutException, + is InterruptedIOException, + is StreamResetException -> R.string.error_timeout + is NotLoggedInException -> R.string.error_login_failed + is PasswordChangeRequiredException -> R.string.error_password_change_required + is ServiceUnavailableException -> R.string.error_service_unavailable + is FeatureDisabledException -> R.string.error_feature_disabled + is FeatureNotAvailableException -> R.string.error_feature_not_available + is BadCredentialsException -> R.string.error_password_invalid + is AccountInactiveException -> R.string.error_account_inactive + is VulcanException -> R.string.error_unknown_uonet + is ScrapperException -> R.string.error_unknown_app + is CloudflareVerificationException -> R.string.error_cloudflare_captcha + is SSLHandshakeException -> when { + error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime + else -> R.string.error_timeout + } + else -> R.string.error_unknown +}.let { getString(it) } + +fun Throwable.isShouldBeReported(): Boolean = when (this) { + is UnknownHostException, + is ConnectException, + is SocketException, + is SocketTimeoutException, + is InterruptedIOException, + is StreamResetException, + is ServiceUnavailableException, + is FeatureDisabledException, + is FeatureNotAvailableException -> false + is SSLHandshakeException -> when { + isCausedByCertificateNotValidNow() -> false + else -> true + } + else -> true +} + +private fun Throwable?.isCausedByCertificateNotValidNow(): Boolean { + var exception = this + do { + if (exception.isCertificateNotValidNow()) return true + + exception = exception?.cause + } while (exception != null) + return false +} + +private fun Throwable?.isCertificateNotValidNow(): Boolean { + val isNotYetValid = this is CertificateNotYetValidException + val isExpired = this is CertificateExpiredException + val isInvalidPath = this is CertPathValidatorException + return isNotYetValid || isExpired || isInvalidPath +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt deleted file mode 100644 index bd6867a3..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FlexibleAdapterExtension.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.utils - -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem - -inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (item: AbstractFlexibleItem<*>) -> Unit) { - addListener(FlexibleAdapter.OnItemClickListener { _, position -> - listener(getItem(position) as AbstractFlexibleItem<*>) - true - }) -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt index 9cec331f..01c876dd 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt @@ -2,16 +2,19 @@ package io.github.wulkanowy.utils import androidx.fragment.app.Fragment import com.ncapdevi.fragnav.FragNavController -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.base.BaseView -inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?) -> Unit) { +inline fun FragNavController.setOnViewChangeListener(crossinline listener: (view: BaseView) -> Unit) { transactionListener = object : FragNavController.TransactionListener { - override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) { - listener(fragment?.toSection()) + override fun onFragmentTransaction( + fragment: Fragment?, + transactionType: FragNavController.TransactionType + ) { + fragment?.let { listener(it as BaseView) } } override fun onTabTransaction(fragment: Fragment?, index: Int) { - listener(fragment?.toSection()) + fragment?.let { listener(it as BaseView) } } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt deleted file mode 100644 index 0b71c964..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.wulkanowy.utils - -import androidx.fragment.app.Fragment -import io.github.wulkanowy.ui.modules.about.AboutFragment -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.more.MoreFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import io.github.wulkanowy.ui.modules.settings.SettingsFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment - -fun Fragment.toSection(): MainView.Section? { - return when (this) { - is GradeFragment -> MainView.Section.GRADE - is AttendanceFragment -> MainView.Section.ATTENDANCE - is ExamFragment -> MainView.Section.EXAM - is TimetableFragment -> MainView.Section.TIMETABLE - is MoreFragment -> MainView.Section.MORE - is MessageFragment -> MainView.Section.MESSAGE - is HomeworkFragment -> MainView.Section.HOMEWORK - is NoteFragment -> MainView.Section.NOTE - is LuckyNumberFragment -> MainView.Section.LUCKY_NUMBER - is SettingsFragment -> MainView.Section.SETTINGS - is AboutFragment -> MainView.Section.ABOUT - is SchoolAndTeachersFragment -> MainView.Section.SCHOOL - else -> null - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt index c57b6247..61924d4e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt @@ -3,76 +3,80 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.sdk.scrapper.grades.getGradeValueWithModifier +import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid -fun List.calcAverage(): Double { +fun List.calcAverage(isOptionalArithmeticAverage: Boolean): Double { + val isArithmeticAverage = isOptionalArithmeticAverage && !any { it.weightValue != .0 } var counter = 0.0 var denominator = 0.0 forEach { - counter += (it.value + it.modifier) * it.weightValue - denominator += it.weightValue + val weight = if (isArithmeticAverage && isGradeValid(it.entry)) 1.0 else it.weightValue + counter += (it.value + it.modifier) * weight + denominator += weight } return if (denominator != 0.0) counter / denominator else 0.0 } -@JvmName("calcSummaryAverage") -fun List.calcAverage(): Double { - return asSequence().mapNotNull { - if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null - }.average().let { if (it.isNaN()) 0.0 else it } -} +fun List.calcFinalAverage(plusModifier: Double, minusModifier: Double) = asSequence() + .mapNotNull { summary -> + val (gradeValue, gradeModifier) = getGradeValueWithModifier(summary.finalGrade) + if (gradeValue == null || gradeModifier == null) return@mapNotNull null -fun Grade.getBackgroundColor(theme: String): Int { - return when (theme) { - "grade_color" -> getGradeColor() - "material" -> when (value.toInt()) { - 6 -> R.color.grade_material_six - 5 -> R.color.grade_material_five - 4 -> R.color.grade_material_four - 3 -> R.color.grade_material_three - 2 -> R.color.grade_material_two - 1 -> R.color.grade_material_one - else -> R.color.grade_material_default - } - else -> when (value.toInt()) { - 6 -> R.color.grade_vulcan_six - 5 -> R.color.grade_vulcan_five - 4 -> R.color.grade_vulcan_four - 3 -> R.color.grade_vulcan_three - 2 -> R.color.grade_vulcan_two - 1 -> R.color.grade_vulcan_one - else -> R.color.grade_vulcan_default + when { + gradeModifier > 0 -> gradeValue + plusModifier + gradeModifier < 0 -> gradeValue - minusModifier + else -> gradeValue + 0.0 } } -} + .average() + .let { if (it.isNaN()) 0.0 else it } -fun Grade.getGradeColor(): Int { - return when (color) { - "000000" -> R.color.grade_black - "F04C4C" -> R.color.grade_red - "20A4F7" -> R.color.grade_blue - "6ECD07" -> R.color.grade_green - "B16CF1" -> R.color.grade_purple - else -> R.color.grade_material_default - } +fun Grade.getGradeColor() = when (color) { + "000000" -> R.color.grade_black + "F04C4C" -> R.color.grade_red + "20A4F7" -> R.color.grade_blue + "6ECD07" -> R.color.grade_green + "B16CF1" -> R.color.grade_purple + else -> R.color.grade_material_default } inline val Grade.colorStringId: Int - get() { - return when (color) { - "000000" -> R.string.all_black - "F04C4C" -> R.string.all_red - "20A4F7" -> R.string.all_blue - "6ECD07" -> R.string.all_green - "B16CF1" -> R.string.all_purple - else -> R.string.all_empty_color - } + get() = when (color) { + "000000" -> R.string.all_black + "F04C4C" -> R.string.all_red + "20A4F7" -> R.string.all_blue + "6ECD07" -> R.string.all_green + "B16CF1" -> R.string.all_purple + else -> R.string.all_empty_color } -fun Grade.changeModifier(plusModifier: Double, minusModifier: Double): Grade { - return when { - modifier > 0 -> copy(modifier = plusModifier) - modifier < 0 -> copy(modifier = -minusModifier) - else -> this +fun Grade.changeModifier(plusModifier: Double, minusModifier: Double) = when { + modifier > 0 -> copy(modifier = plusModifier) + modifier < 0 -> copy(modifier = -minusModifier) + else -> this +} + +fun Grade.getBackgroundColor(theme: GradeColorTheme) = when (theme) { + GradeColorTheme.GRADE_COLOR -> getGradeColor() + GradeColorTheme.MATERIAL -> when (value.toInt()) { + 6 -> R.color.grade_material_six + 5 -> R.color.grade_material_five + 4 -> R.color.grade_material_four + 3 -> R.color.grade_material_three + 2 -> R.color.grade_material_two + 1 -> R.color.grade_material_one + else -> R.color.grade_material_default + } + GradeColorTheme.VULCAN -> when (value.toInt()) { + 6 -> R.color.grade_vulcan_six + 5 -> R.color.grade_vulcan_five + 4 -> R.color.grade_vulcan_four + 3 -> R.color.grade_vulcan_three + 2 -> R.color.grade_vulcan_two + 1 -> R.color.grade_vulcan_one + else -> R.color.grade_vulcan_default } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt new file mode 100644 index 00000000..62b85af4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt @@ -0,0 +1,121 @@ +package io.github.wulkanowy.utils + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.CalendarContract +import android.provider.Settings +import io.github.wulkanowy.BuildConfig +import timber.log.Timber +import java.time.LocalDateTime +import java.time.ZoneId + +fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) { + Intent.parseUri(uri, 0).let { + try { + startActivity(it) + } catch (e: ActivityNotFoundException) { + onActivityNotFound(uri) + } + } +} + +fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) { + openInternetBrowser("market://details?id=${BuildConfig.APPLICATION_ID}") { + openInternetBrowser("https://github.com/wulkanowy/wulkanowy/releases", onActivityNotFound) + } +} + +fun Context.openEmailClient( + chooserTitle: String, + email: String, + subject: String, + body: String, + onActivityNotFound: () -> Unit = {} +) { + val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply { + putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, body) + } + + if (intent.resolveActivity(packageManager) != null) { + startActivity(Intent.createChooser(intent, chooserTitle)) + } else onActivityNotFound() +} + +fun Context.openCalendarEventAdd( + title: String, + description: String, + start: LocalDateTime, + end: LocalDateTime? = null, + isAllDay: Boolean = false, + onActivityNotFound: (uri: String?) -> Unit = {}, +) { + val beginTime = start.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() + val endTime = end?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli() + + val intent = Intent(Intent.ACTION_INSERT) + .setData(CalendarContract.Events.CONTENT_URI) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime) + .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime) + .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) + .putExtra(CalendarContract.Events.TITLE, title) + .putExtra(CalendarContract.Events.DESCRIPTION, description) + .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) + + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + onActivityNotFound(intent.dataString) + } +} + +fun Context.openNavigation(location: String) { + val intentUri = Uri.parse("geo:0,0?q=${Uri.encode(location)}") + val intent = Intent(Intent.ACTION_VIEW, intentUri) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } +} + +fun Context.openDialer(phone: String) { + val intentUri = Uri.parse("tel:$phone") + val intent = Intent(Intent.ACTION_DIAL, intentUri) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } +} + +fun Activity.openNotificationSettings() { + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra("android.provider.extra.APP_PACKAGE", packageName) + } + } else { + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + } + } + try { + startActivity(intent) + } catch (e: Exception) { + Timber.e(e) + } +} + +fun Context.shareText(text: String, subject: String?) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + if (subject != null) { + putExtra(Intent.EXTRA_SUBJECT, subject) + } + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt new file mode 100644 index 00000000..76ce66dc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt @@ -0,0 +1,61 @@ +package io.github.wulkanowy.utils + +import android.os.Handler +import android.os.Looper +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +class LifecycleAwareVariable : ReadWriteProperty, DefaultLifecycleObserver { + + private var _value: T? = null + + override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { + thisRef.viewLifecycleOwner.lifecycle.removeObserver(this) + _value = value + thisRef.viewLifecycleOwner.lifecycle.addObserver(this) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>) = _value + ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") + + override fun onDestroy(owner: LifecycleOwner) { + Handler(Looper.getMainLooper()).post { + _value = null + } + } +} + +class LifecycleAwareVariableComponent : ReadWriteProperty, + DefaultLifecycleObserver { + + private var _value: T? = null + + override fun setValue(thisRef: LifecycleOwner, property: KProperty<*>, value: T) { + thisRef.lifecycle.removeObserver(this) + _value = value + thisRef.lifecycle.addObserver(this) + } + + override fun getValue(thisRef: LifecycleOwner, property: KProperty<*>) = _value + ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") + + override fun onDestroy(owner: LifecycleOwner) { + Handler(Looper.getMainLooper()).post { + _value = null + } + } +} + +@Suppress("unused") +fun Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() + +@Suppress("unused") +fun DialogFragment.lifecycleAwareVariable() = LifecycleAwareVariableComponent() + +@Suppress("unused") +fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableComponent() diff --git a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt index fc352849..00ed2c11 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt @@ -7,7 +7,13 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import fr.bipi.treessence.common.filters.Filter +import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import timber.log.Timber +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -18,43 +24,62 @@ class DebugLogTree : Timber.DebugTree() { } } -private fun Bundle?.checkSavedState() = if (this == null) "(STATE IS NULL)" else "(STATE IS NOT NULL)" +object ExceptionFilter : Filter { + + override fun isLoggable(priority: Int, tag: String?) = true + + override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?) = + when (t) { + is FeatureDisabledException, + is FeatureNotAvailableException, + is UnknownHostException, + is SocketTimeoutException, + is InterruptedIOException -> true + else -> false + } +} class ActivityLifecycleLogger : Application.ActivityLifecycleCallbacks { - override fun onActivityPaused(activity: Activity?) { - activity?.let { Timber.d("${it::class.java.simpleName} PAUSED") } + override fun onActivityPaused(activity: Activity) { + Timber.d("${activity::class.java.simpleName} PAUSED") } - override fun onActivityResumed(activity: Activity?) { - activity?.let { Timber.d("${it::class.java.simpleName} RESUMED") } + override fun onActivityResumed(activity: Activity) { + Timber.d("${activity::class.java.simpleName} RESUMED") } - override fun onActivityStarted(activity: Activity?) { - activity?.let { Timber.d("${it::class.java.simpleName} STARTED") } + override fun onActivityStarted(activity: Activity) { + Timber.d("${activity::class.java.simpleName} STARTED") } - override fun onActivityDestroyed(activity: Activity?) { - activity?.let { Timber.d("${it::class.java.simpleName} DESTROYED") } + override fun onActivityDestroyed(activity: Activity) { + Timber.d("${activity::class.java.simpleName} DESTROYED") } - override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { - activity?.let { Timber.d("${it::class.java.simpleName} SAVED INSTANCE STATE") } + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + Timber.d("${activity::class.java.simpleName} SAVED INSTANCE STATE") } - override fun onActivityStopped(activity: Activity?) { - activity?.let { Timber.d("${it::class.java.simpleName} STOPPED") } + override fun onActivityStopped(activity: Activity) { + Timber.d("${activity::class.java.simpleName} STOPPED") } - override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { - activity?.let { Timber.d("${it::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}") } + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + Timber.d("${activity::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}") } } @Singleton -class FragmentLifecycleLogger @Inject constructor() : FragmentManager.FragmentLifecycleCallbacks() { +class FragmentLifecycleLogger @Inject constructor() : + FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) { + override fun onFragmentViewCreated( + fm: FragmentManager, + f: Fragment, + v: View, + savedInstanceState: Bundle? + ) { Timber.d("${f::class.java.simpleName} VIEW CREATED ${savedInstanceState.checkSavedState()}") } @@ -62,7 +87,11 @@ class FragmentLifecycleLogger @Inject constructor() : FragmentManager.FragmentLi Timber.d("${f::class.java.simpleName} STOPPED") } - override fun onFragmentCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) { + override fun onFragmentCreated( + fm: FragmentManager, + f: Fragment, + savedInstanceState: Bundle? + ) { Timber.d("${f::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}") } @@ -78,7 +107,11 @@ class FragmentLifecycleLogger @Inject constructor() : FragmentManager.FragmentLi Timber.d("${f::class.java.simpleName} DESTROYED") } - override fun onFragmentSaveInstanceState(fm: FragmentManager, f: Fragment, outState: Bundle) { + override fun onFragmentSaveInstanceState( + fm: FragmentManager, + f: Fragment, + outState: Bundle + ) { Timber.d("${f::class.java.simpleName} SAVED INSTANCE STATE") } @@ -90,10 +123,6 @@ class FragmentLifecycleLogger @Inject constructor() : FragmentManager.FragmentLi Timber.d("${f::class.java.simpleName} VIEW DESTROYED") } - override fun onFragmentActivityCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) { - Timber.d("${f::class.java.simpleName} ACTIVITY CREATED ${savedInstanceState.checkSavedState()}") - } - override fun onFragmentPaused(fm: FragmentManager, f: Fragment) { Timber.d("${f::class.java.simpleName} PAUSED") } @@ -102,3 +131,7 @@ class FragmentLifecycleLogger @Inject constructor() : FragmentManager.FragmentLi Timber.d("${f::class.java.simpleName} DETACHED") } } + +private fun Bundle?.checkSavedState() = + if (this == null) "(STATE IS NULL)" else "(RESTORE STATE)" + diff --git a/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt new file mode 100644 index 00000000..09ccda89 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.utils + +import androidx.fragment.app.Fragment +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.MaterialDatePicker +import kotlinx.parcelize.Parcelize +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +fun Fragment.openMaterialDatePicker( + selected: LocalDate, + rangeStart: LocalDate, + rangeEnd: LocalDate, + onDateSelected: (LocalDate) -> Unit, +) { + val constraintsBuilder = CalendarConstraints.Builder().apply { + setValidator(CalendarDayRangeValidator(rangeStart, rangeEnd)) + setStart(rangeStart.toTimestamp()) + setEnd(rangeEnd.toTimestamp()) + } + + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(selected.toTimestamp()) + .build() + + datePicker.addOnPositiveButtonClickListener { + val date = it.toLocalDateTime().toLocalDate() + onDateSelected(date) + } + + if (!parentFragmentManager.isStateSaved) { + datePicker.show(parentFragmentManager, null) + } +} + +@Parcelize +private class CalendarDayRangeValidator( + val start: LocalDate, + val end: LocalDate, +) : CalendarConstraints.DateValidator { + + override fun isValid(dateLong: Long): Boolean { + val date = dateLong.toLocalDateTime().toLocalDate() + val daysUntilEnd = date.until(end, ChronoUnit.DAYS) + val daysUntilStart = date.until(start, ChronoUnit.DAYS) + + return daysUntilStart <= 0 && daysUntilEnd >= 0 + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt b/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt new file mode 100644 index 00000000..45ee431a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.utils + +import android.app.PendingIntent +import android.os.Build + +object PendingIntentCompat { + + val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt new file mode 100644 index 00000000..e43654b8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -0,0 +1,57 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.enums.MessageFolder +import timber.log.Timber +import java.time.Duration.ofMinutes +import java.time.Instant +import java.time.LocalDate +import javax.inject.Inject + +fun getRefreshKey(name: String, semester: Semester, start: LocalDate, end: LocalDate): String { + return "${name}_${semester.studentId}_${semester.semesterId}_${start.monday}_${end.sunday}" +} + +fun getRefreshKey(name: String, semester: Semester): String { + return "${name}_${semester.studentId}_${semester.semesterId}" +} + +fun getRefreshKey(name: String, student: Student): String { + return "${name}_${student.studentId}" +} + +fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String { + return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}" +} + +class AutoRefreshHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val sharedPref: SharedPrefProvider +) { + + fun shouldBeRefreshed(key: String): Boolean { + val timestamp = sharedPref.getLong(key, 0).let(Instant::ofEpochMilli) + val servicesInterval = sharedPref.getString(context.getString(R.string.pref_key_services_interval), context.getString(R.string.pref_default_services_interval)).toLong() + + val shouldBeRefreshed = timestamp < Instant.now().minus(ofMinutes(servicesInterval)) + + Timber.d("Check if $key need to be refreshed: $shouldBeRefreshed (last refresh: $timestamp, interval: $servicesInterval min)") + + return shouldBeRefreshed + } + + fun updateLastRefreshTimestamp(key: String) { + sharedPref.putLong(key, Instant.now().toEpochMilli()) + } + + fun getLastRefreshTimestamp(key: String): Instant { + val refreshTimestampMilli = sharedPref.getLong(key, 0) + return Instant.ofEpochMilli(refreshTimestampMilli) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt new file mode 100644 index 00000000..6e8f7ae6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.utils + +enum class RemoteConfigDefaults(val value: Any) { + USER_AGENT_TEMPLATE(""), + ; + + val key get() = name.lowercase() +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt deleted file mode 100644 index 9744d374..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.wulkanowy.utils - -import android.content.res.Resources -import io.github.wulkanowy.R -import io.github.wulkanowy.sdk.exception.FeatureDisabledException -import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException -import io.github.wulkanowy.sdk.exception.NotLoggedInException -import io.github.wulkanowy.sdk.exception.ServiceUnavailableException -import java.net.SocketTimeoutException -import java.net.UnknownHostException - -fun Resources.getString(error: Throwable) = when (error) { - is UnknownHostException -> getString(R.string.error_no_internet) - is SocketTimeoutException -> getString(R.string.error_timeout) - is NotLoggedInException -> getString(R.string.error_login_failed) - is ServiceUnavailableException -> getString(R.string.error_service_unavailable) - is FeatureDisabledException -> getString(R.string.error_feature_disabled) - is FeatureNotAvailableException -> getString(R.string.error_feature_not_available) - else -> getString(R.string.error_unknown) -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/SchedulersProvider.kt b/app/src/main/java/io/github/wulkanowy/utils/SchedulersProvider.kt deleted file mode 100644 index e426d4d5..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/SchedulersProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.utils - -import io.reactivex.Scheduler -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers - -open class SchedulersProvider { - - open val mainThread: Scheduler - get() = AndroidSchedulers.mainThread() - - open val backgroundThread: Scheduler - get() = Schedulers.io() -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt b/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt deleted file mode 100644 index 922aafbd..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysRangeLimiter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.wulkanowy.utils - -import android.os.Parcel -import android.os.Parcelable -import com.wdullaer.materialdatetimepicker.date.DateRangeLimiter -import org.threeten.bp.DayOfWeek -import org.threeten.bp.LocalDate -import java.util.Calendar - -@Suppress("UNUSED_PARAMETER") -class SchooldaysRangeLimiter : DateRangeLimiter { - - private val now = LocalDate.now() - - override fun setToNearestDate(day: Calendar): Calendar = day - - override fun isOutOfRange(year: Int, month: Int, day: Int): Boolean { - val date = LocalDate.of(year, month + 1, day) - val dayOfWeek = date.dayOfWeek - return dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY || date.isHolidays - } - - override fun getStartDate(): Calendar { - val startYear = if (now.monthValue <= 6) now.year - 1 else now.year - val startOfSchoolYear = now.withYear(startYear).firstSchoolDay - - val calendar = Calendar.getInstance() - calendar.set(startOfSchoolYear.year, startOfSchoolYear.monthValue - 1, startOfSchoolYear.dayOfMonth) - return calendar - } - - override fun getEndDate(): Calendar { - val endYear = if (now.monthValue > 6) now.year + 1 else now.year - val endOfSchoolYear = now.withYear(endYear).lastSchoolDay - - val calendar = Calendar.getInstance() - calendar.set(endOfSchoolYear.year, endOfSchoolYear.monthValue - 1, endOfSchoolYear.dayOfMonth) - return calendar - } - - override fun writeToParcel(parcel: Parcel, flags: Int) {} - - override fun describeContents() = 0 - - companion object CREATOR : Parcelable.Creator { - - override fun createFromParcel(parcel: Parcel): SchooldaysRangeLimiter = SchooldaysRangeLimiter() - - override fun newArray(size: Int): Array = arrayOfNulls(size) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt deleted file mode 100644 index e4d4163b..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.utils - -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import timber.log.Timber - -fun Sdk.init(student: Student): Sdk { - email = student.email - password = student.password - symbol = student.symbol - schoolSymbol = student.schoolSymbol - studentId = student.studentId - classId = student.classId - - if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { - scrapperBaseUrl = student.scrapperBaseUrl - loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) - } - loginId = student.userLoginId - - mode = Sdk.Mode.valueOf(student.loginMode) - mobileBaseUrl = student.mobileBaseUrl - certKey = student.certificateKey - privateKey = student.privateKey - - Timber.d("Sdk in ${student.loginMode} mode reinitialized") - - return this -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt index b3c479c3..6cfc4fa1 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt @@ -1,19 +1,33 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.data.db.entities.Semester -import org.threeten.bp.LocalDate.now +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.Month -inline val Semester.isCurrent: Boolean - get() = now() in start..end +fun Semester.isCurrent(now: LocalDate = now()): Boolean { + val shiftedStart = if (start.month == Month.SEPTEMBER) { + start.minusDays(3) + } else start + + val shiftedEnd = if (end.month == Month.AUGUST || end.month == Month.SEPTEMBER) { + end.minusDays(3) + } else end + + return now in shiftedStart..shiftedEnd +} fun List.getCurrentOrLast(): Semester { - if (isEmpty()) throw RuntimeException("Empty semester list") + if (isEmpty()) throw IllegalStateException("Empty semester list") // when there is only one current semester - singleOrNull { it.isCurrent }?.let { return it } + singleOrNull { it.isCurrent() }?.let { return it } // when there is more than one current semester - find one with higher id - singleOrNull { semester -> semester.semesterId == maxBy { it.semesterId }?.semesterId }?.let { return it } + singleOrNull { semester -> semester.semesterId == maxByOrNull { it.semesterId }?.semesterId }?.let { return it } + + // when there is no active kindergarten semester - get one from last year + singleOrNull { semester -> semester.schoolYear == maxByOrNull { it.schoolYear }?.schoolYear }?.let { return it } throw IllegalArgumentException("Duplicated last semester! Semesters: ${joinToString(separator = "\n")}") } diff --git a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt index 65167fd7..8043e365 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt @@ -1,3 +1,15 @@ package io.github.wulkanowy.utils -inline fun String?.ifNullOrBlank(defaultValue: () -> String) = if (this.isNullOrBlank()) defaultValue() else this +import androidx.core.text.parseAsHtml +import org.apache.commons.text.StringEscapeUtils + +inline fun String?.ifNullOrBlank(defaultValue: () -> String) = + if (isNullOrBlank()) defaultValue() else this + +fun String.capitalise() = + replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + +fun String.parseUonetHtml() = this + .let(StringEscapeUtils::unescapeHtml4) + .replace("\n", "
") + .parseAsHtml() diff --git a/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt new file mode 100644 index 00000000..132a3085 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt @@ -0,0 +1,5 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.Student + +inline val Student.nickOrName get() = nick.ifBlank { studentName } diff --git a/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt new file mode 100644 index 00000000..e9135f49 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt @@ -0,0 +1,66 @@ +package io.github.wulkanowy.utils + +import android.annotation.SuppressLint +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView + +/** + * Custom alternative to androidx.recyclerview.widget.ListAdapter. ListAdapter is asynchronous which + * caused data race problems in views when a Resource.Error arrived shortly after + * Resource.Intermediate/Success - occasionally in that case the user could see both the Resource's + * data and an error message one on top of the other. This is synchronized by design to avoid that + * problem, however it retains the quality of life improvements of the original. + */ +abstract class SyncListAdapter private constructor( + private val updateStrategy: SyncListAdapter.(List) -> Unit +) : RecyclerView.Adapter() { + + constructor(differ: DiffUtil.ItemCallback) : this({ newItems -> + val diffResult = DiffUtil.calculateDiff(toCallback(differ, items, newItems)) + items = newItems + diffResult.dispatchUpdatesTo(this) + }) + + var items = emptyList() + private set + + final override fun getItemCount() = items.size + + fun getItem(position: Int): T { + return items[position] + } + + /** + * Updates all items, same as submitList, however also disables animations temporarily. + * This prevents a flashing effect on some views. Should be used in favor of submitList when + * all data is changed (e.g. the selected day changes in timetable causing all lessons to change). + */ + @SuppressLint("NotifyDataSetChanged") + fun recreate(data: List) { + items = data + notifyDataSetChanged() + } + + fun submitList(data: List) { + updateStrategy(data.toList()) + } + + private fun toCallback( + itemCallback: DiffUtil.ItemCallback, + old: List, + new: List, + ) = object : DiffUtil.Callback() { + override fun getOldListSize() = old.size + + override fun getNewListSize() = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.areItemsTheSame(old[oldItemPosition], new[newItemPosition]) + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.areContentsTheSame(old[oldItemPosition], new[newItemPosition]) + + override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.getChangePayload(old[oldItemPosition], new[newItemPosition]) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index a91f823f..e7a50d0c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -1,49 +1,40 @@ package io.github.wulkanowy.utils -import org.threeten.bp.DayOfWeek.FRIDAY -import org.threeten.bp.DayOfWeek.MONDAY -import org.threeten.bp.DayOfWeek.SATURDAY -import org.threeten.bp.DayOfWeek.SUNDAY -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime -import org.threeten.bp.Month -import org.threeten.bp.format.DateTimeFormatter.ofPattern -import org.threeten.bp.format.TextStyle.FULL_STANDALONE -import org.threeten.bp.temporal.TemporalAdjusters.firstInMonth -import org.threeten.bp.temporal.TemporalAdjusters.next -import org.threeten.bp.temporal.TemporalAdjusters.previous -import java.util.Locale +import java.text.SimpleDateFormat +import java.time.* +import java.time.DayOfWeek.* +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAdjusters.* +import java.util.* -private const val DATE_PATTERN = "dd.MM.yyyy" +private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy" -fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate = LocalDate.parse(this, ofPattern(format)) +fun LocalDate.toTimestamp(): Long = atStartOfDay() + .toInstant(ZoneOffset.UTC) + .toEpochMilli() -fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) +fun Long.toLocalDateTime(): LocalDateTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(this), ZoneOffset.UTC +) -fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) +fun Instant.toLocalDate(): LocalDate = atZone(ZoneOffset.UTC).toLocalDate() + +fun String.toLocalDate(format: String = DEFAULT_DATE_PATTERN): LocalDate = + LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) + +fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String = + format(DateTimeFormatter.ofPattern(pattern)) + +fun Instant.toFormattedString( + pattern: String = DEFAULT_DATE_PATTERN, + tz: ZoneId = ZoneId.systemDefault() +): String = atZone(tz).format(DateTimeFormatter.ofPattern(pattern)) -/** - * https://github.com/ThreeTen/threetenbp/issues/55 - */ fun Month.getFormattedName(): String { - return getDisplayName(FULL_STANDALONE, Locale.getDefault()) - .let { - when (it) { - "stycznia" -> "Styczeń" - "lutego" -> "Luty" - "marca" -> "Marzec" - "kwietnia" -> "Kwiecień" - "maja" -> "Maj" - "czerwca" -> "Czerwiec" - "lipca" -> "Lipiec" - "sierpnia" -> "Sierpień" - "września" -> "Wrzesień" - "października" -> "Październik" - "listopada" -> "Listopad" - "grudnia" -> "Grudzień" - else -> it - } - } + val formatter = SimpleDateFormat("LLLL", Locale.getDefault()) + + val date = LocalDateTime.now().withMonth(value) + return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalise() } inline val LocalDate.nextSchoolDay: LocalDate @@ -70,6 +61,12 @@ inline val LocalDate.nextOrSameSchoolDay: LocalDate } } +inline val LocalDate.startExamsDay: LocalDate + get() = nextOrSameSchoolDay.monday + +inline val LocalDate.endExamsDay: LocalDate + get() = nextOrSameSchoolDay.monday.plusWeeks(4).minusDays(1) + inline val LocalDate.previousOrSameSchoolDay: LocalDate get() { return when (dayOfWeek) { @@ -79,31 +76,33 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate } inline val LocalDate.weekDayName: String - get() = format(ofPattern("EEEE", Locale.getDefault())) + get() = format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault())) -inline val LocalDate.monday: LocalDate - get() = with(MONDAY) +inline val LocalDate.monday: LocalDate get() = with(MONDAY) -inline val LocalDate.friday: LocalDate - get() = with(FRIDAY) +inline val LocalDate.sunday: LocalDate get() = with(SUNDAY) /** * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) */ -inline val LocalDate.isHolidays: Boolean - get() = isBefore(firstSchoolDay) && isAfter(lastSchoolDay) +val LocalDate.isHolidays: Boolean + get() = isBefore(firstSchoolDayInCalendarYear) && isAfter(lastSchoolDayInCalendarYear) -inline val LocalDate.firstSchoolDay: LocalDate - get() = LocalDate.of(year, 9, 1).run { - when (dayOfWeek) { - FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY)) - else -> this - } +val LocalDate.firstSchoolDayInSchoolYear: LocalDate + get() = withYear(if (this.monthValue <= 6) this.year - 1 else this.year).firstSchoolDayInCalendarYear + +val LocalDate.lastSchoolDayInSchoolYear: LocalDate + get() = withYear(if (this.monthValue > 6) this.year + 1 else this.year).lastSchoolDayInCalendarYear + +fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate { + val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth) + + if (date.isHolidays) { + return date.lastSchoolDayInCalendarYear } -inline val LocalDate.lastSchoolDay: LocalDate - get() = LocalDate.of(year, 6, 20) - .with(next(FRIDAY)) + return date +} private fun Int.getSchoolYearByMonth(monthValue: Int): Int { return when (monthValue) { @@ -112,12 +111,15 @@ private fun Int.getSchoolYearByMonth(monthValue: Int): Int { } } -fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate { - val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth) - - if (date.isHolidays) { - return date.lastSchoolDay +private inline val LocalDate.firstSchoolDayInCalendarYear: LocalDate + get() = LocalDate.of(year, 9, 1).run { + when (dayOfWeek) { + FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY)) + else -> this + } } - return date -} +private inline val LocalDate.lastSchoolDayInCalendarYear: LocalDate + get() = LocalDate.of(year, 6, 20) + .with(next(FRIDAY)) + diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt new file mode 100644 index 00000000..3e94463b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.Duration +import java.time.Duration.between +import java.time.Instant +import java.time.Instant.now + +fun Timetable.isShowTimeUntil(previousLessonEnd: Instant?) = when { + !isStudentPlan -> false + canceled -> false + now().isAfter(start) -> false + previousLessonEnd != null && now().isBefore(previousLessonEnd) -> false + else -> between(now(), start) <= Duration.ofMinutes(60) +} + +inline val Timetable.left: Duration? + get() = when { + canceled -> null + !isStudentPlan -> null + end >= now() && start <= now() -> between(now(), end) + else -> null + } + +inline val Timetable.until: Duration + get() = between(now(), start) + +inline val Timetable.isJustFinished: Boolean + get() = end.isBefore(now()) && end.plusSeconds(15).isAfter(now()) && !canceled diff --git a/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt index 6a5ad880..700ac2f1 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt @@ -1,13 +1,11 @@ package io.github.wulkanowy.utils -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 -inline fun ViewPager.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) { - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { +inline fun ViewPager2.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) { + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { selectListener(position) } - override fun onPageScrollStateChanged(state: Int) {} - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} }) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt new file mode 100644 index 00000000..4d2dde78 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -0,0 +1,74 @@ +package io.github.wulkanowy.utils + +import android.util.AndroidRuntimeException +import java.net.CookiePolicy +import java.net.CookieStore +import java.net.HttpCookie +import java.net.URI +import javax.inject.Inject +import javax.inject.Singleton +import android.webkit.CookieManager as WebkitCookieManager +import java.net.CookieManager as JavaCookieManager + +@Singleton +class WebkitCookieManagerProxy @Inject constructor() : + JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { + + val webkitCookieManager: WebkitCookieManager? = getCookieManager() + + /** + * @see [https://stackoverflow.com/a/70354583/6695449] + */ + private fun getCookieManager(): WebkitCookieManager? { + return try { + WebkitCookieManager.getInstance() + } catch (e: AndroidRuntimeException) { + null + } + } + + override fun put(uri: URI?, responseHeaders: Map>?) { + if (uri == null || responseHeaders == null) return + val url = uri.toString() + for (headerKey in responseHeaders.keys) { + if (headerKey == null || !( + headerKey.equals("Set-Cookie2", ignoreCase = true) || + headerKey.equals("Set-Cookie", ignoreCase = true) + ) + ) continue + + // process each of the headers + for (headerValue in responseHeaders[headerKey].orEmpty()) { + webkitCookieManager?.setCookie(url, headerValue) + } + } + } + + override operator fun get( + uri: URI?, + requestHeaders: Map?>? + ): Map> { + require(!(uri == null || requestHeaders == null)) { "Argument is null" } + val res = mutableMapOf>() + val cookie = webkitCookieManager?.getCookie(uri.toString()) + if (cookie != null) res["Cookie"] = listOf(cookie) + return res + } + + override fun getCookieStore(): CookieStore { + val cookies = super.getCookieStore() + return object : CookieStore { + override fun add(uri: URI?, cookie: HttpCookie?) = cookies.add(uri, cookie) + override fun get(uri: URI?): List = cookies.get(uri) + override fun getCookies(): List = cookies.cookies + override fun getURIs(): List = cookies.urIs + override fun remove(uri: URI?, cookie: HttpCookie?): Boolean = + cookies.remove(uri, cookie) + + override fun removeAll(): Boolean { + webkitCookieManager?.removeAllCookies(null) ?: return false + return true + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index 264f4542..db16a256 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -2,10 +2,8 @@ package io.github.wulkanowy.utils.security -import android.annotation.TargetApi import android.content.Context import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2 import android.os.Build.VERSION_CODES.M import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec @@ -18,6 +16,7 @@ import android.util.Base64.DEFAULT import android.util.Base64.decode import android.util.Base64.encode import android.util.Base64.encodeToString +import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -35,114 +34,124 @@ import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream import javax.crypto.spec.OAEPParameterSpec import javax.crypto.spec.PSource.PSpecified +import javax.inject.Inject +import javax.inject.Singleton import javax.security.auth.x500.X500Principal -private const val KEYSTORE_NAME = "AndroidKeyStore" +@Singleton +class Scrambler @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val keyCharset = Charset.forName("UTF-8") -private const val KEY_ALIAS = "wulkanowy_password" + private val isKeyPairExists: Boolean + get() = keyStore.getKey(KEY_ALIAS, null) != null -private val KEY_CHARSET = Charset.forName("UTF-8") + private val keyStore: KeyStore + get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } -private val isKeyPairExists: Boolean - get() = keyStore.getKey(KEY_ALIAS, null) != null + private val cipher: Cipher + get() { + return if (SDK_INT >= M) Cipher.getInstance( + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + "AndroidKeyStoreBCWorkaround" + ) + else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL") + } -private val keyStore: KeyStore - get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } + fun encrypt(plainText: String): String { + if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") -private val cipher: Cipher - get() { - return if (SDK_INT >= M) Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", "AndroidKeyStoreBCWorkaround") - else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL") + return try { + if (!isKeyPairExists) generateKeyPair() + + cipher.let { + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec) + } + } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) + + ByteArrayOutputStream().let { output -> + CipherOutputStream(output, it).apply { + write(plainText.toByteArray(keyCharset)) + close() + } + encodeToString(output.toByteArray(), DEFAULT) + } + } + } catch (exception: Exception) { + Timber.e(exception, "An error occurred while encrypting text") + String(encode(plainText.toByteArray(keyCharset), DEFAULT), keyCharset) + } } -fun encrypt(plainText: String, context: Context): String { - if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") + fun decrypt(cipherText: String): String { + if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - if (SDK_INT < JELLY_BEAN_MR2) { - return String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + return try { + if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") + + cipher.let { + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec) + } + } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null)) + + CipherInputStream( + ByteArrayInputStream(decode(cipherText, DEFAULT)), + it + ).let { input -> + val values = ArrayList() + var nextByte: Int + while (run { nextByte = input.read(); nextByte } != -1) { + values.add(nextByte.toByte()) + } + val bytes = ByteArray(values.size) + for (i in bytes.indices) { + bytes[i] = values[i] + } + String(bytes, 0, bytes.size, keyCharset) + } + } + } catch (e: Exception) { + throw ScramblerException("An error occurred while decrypting text", e) + } } - return try { - if (!isKeyPairExists) generateKeyPair(context) - - cipher.let { - if (SDK_INT >= M) { - OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> - it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec) - } - } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) - - ByteArrayOutputStream().let { output -> - CipherOutputStream(output, it).apply { - write(plainText.toByteArray(KEY_CHARSET)) - close() - } - encodeToString(output.toByteArray(), DEFAULT) + private fun generateKeyPair() { + (if (SDK_INT >= M) { + KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) + .setDigests(DIGEST_SHA256, DIGEST_SHA512) + .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP) + .setCertificateSerialNumber(BigInteger.TEN) + .setCertificateSubject(X500Principal("CN=Wulkanowy")) + .build() + } else { + KeyPairGeneratorSpec.Builder(context) + .setAlias(KEY_ALIAS) + .setSubject(X500Principal("CN=Wulkanowy")) + .setSerialNumber(BigInteger.TEN) + .setStartDate(Calendar.getInstance().time) + .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) + .build() + }).let { + KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply { + initialize(it) + genKeyPair() } } - } catch (exception: Exception) { - Timber.e(exception, "An error occurred while encrypting text") - String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + Timber.i("A new KeyPair has been generated") + } + + fun clearKeyPair() { + keyStore.deleteEntry(KEY_ALIAS) + Timber.i("KeyPair has been cleared") + } + + private companion object { + private const val KEYSTORE_NAME = "AndroidKeyStore" + private const val KEY_ALIAS = "wulkanowy_password" } } - -fun decrypt(cipherText: String): String { - if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - - return try { - if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { - return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) - } - - if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") - - cipher.let { - if (SDK_INT >= M) { - OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> - it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec) - } - } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null)) - - CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input -> - val values = ArrayList() - var nextByte = 0 - while ({ nextByte = input.read(); nextByte }() != -1) { - values.add(nextByte.toByte()) - } - val bytes = ByteArray(values.size) - for (i in bytes.indices) { - bytes[i] = values[i] - } - String(bytes, 0, bytes.size, KEY_CHARSET) - } - } - } catch (e: Exception) { - throw ScramblerException("An error occurred while decrypting text", e) - } -} - -@TargetApi(JELLY_BEAN_MR2) -private fun generateKeyPair(context: Context) { - (if (SDK_INT >= M) { - KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) - .setDigests(DIGEST_SHA256, DIGEST_SHA512) - .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP) - .setCertificateSerialNumber(BigInteger.TEN) - .setCertificateSubject(X500Principal("CN=Wulkanowy")) - .build() - } else { - KeyPairGeneratorSpec.Builder(context) - .setAlias(KEY_ALIAS) - .setSubject(X500Principal("CN=Wulkanowy")) - .setSerialNumber(BigInteger.TEN) - .setStartDate(Calendar.getInstance().time) - .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) - .build() - }).let { - KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply { - initialize(it) - genKeyPair() - } - } - Timber.i("A new KeyPair has been generated") -} diff --git a/app/src/main/play/listings/cs-CZ/full-description.txt b/app/src/main/play/listings/cs-CZ/full-description.txt new file mode 100644 index 00000000..3d142ebf --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/full-description.txt @@ -0,0 +1,14 @@ +Aplikace je určena pro uživatele deníku VULCAN UONET+. + +Zvýrazněné vlastnosti a funkce: +- výpočet váženého průměru, +- procentuální zobrazení docházky, +- šťastné číslo, +- náhled na další a dokončené lekce, +- tmavý motiv, +- volitelné reklamy, +- offline režim, +- upozornění. + +GitHub: https://github.com/wulkanowy/wulkanowy +Discord: https://discord.gg/vccAQBr diff --git a/app/src/main/play/listings/cs-CZ/short-description.txt b/app/src/main/play/listings/cs-CZ/short-description.txt new file mode 100644 index 00000000..0f29ab1b --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/short-description.txt @@ -0,0 +1 @@ +Neoficiální aplikace žáka a rodiče pro deníku VULCAN UONET+ diff --git a/app/src/main/play/listings/cs-CZ/title.txt b/app/src/main/play/listings/cs-CZ/title.txt new file mode 100644 index 00000000..b7f42a5b --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/title.txt @@ -0,0 +1 @@ +Wulkanowy Deníček diff --git a/app/src/main/play/listings/pl-PL/full-description.txt b/app/src/main/play/listings/pl-PL/full-description.txt index 641b7706..b0193b5d 100644 --- a/app/src/main/play/listings/pl-PL/full-description.txt +++ b/app/src/main/play/listings/pl-PL/full-description.txt @@ -4,9 +4,9 @@ Wyróżnione cechy i funkcje: - obliczanie średniej ważonej, - procentowy podgląd frekwencji, - szczęśliwy numerek, -- podgląd lekcji zrealizowanych, +- podgląd lekcji dodatkowych i zrealizowanych, - ciemny motyw. -- brak reklam, +- opcjonalne reklam, - tryb offline, - powiadomienia. diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-grades.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-grades.png deleted file mode 100644 index 56768bf5..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-grades.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-start.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-start.jpg new file mode 100644 index 00000000..19b96d9a Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-start.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2-timetable-dialog.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2-timetable-dialog.png deleted file mode 100644 index 83d9353a..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2-timetable-dialog.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2.jpg new file mode 100644 index 00000000..fe81aadc Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/2.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-exams.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-exams.png deleted file mode 100644 index f83a7af8..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-exams.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-timetable-dialog.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-timetable-dialog.jpg new file mode 100644 index 00000000..0e62c2e1 Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-timetable-dialog.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-exams.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-exams.jpg new file mode 100644 index 00000000..980993af Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-exams.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-timetable-widget.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-timetable-widget.png deleted file mode 100644 index 90d5fef4..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-timetable-widget.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-messages.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-messages.png deleted file mode 100644 index eadbf2db..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-messages.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-timetable-widget.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-timetable-widget.jpg new file mode 100644 index 00000000..6ee7eeb9 Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/5-timetable-widget.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-account-switcher.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-account-switcher.png deleted file mode 100644 index 2a11a3db..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-account-switcher.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-class-grades.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-class-grades.jpg new file mode 100644 index 00000000..ec86cdb0 Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/6-class-grades.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-account-switcher.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-account-switcher.jpg new file mode 100644 index 00000000..66c0db40 Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-account-switcher.jpg differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-class-grades.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-class-grades.png deleted file mode 100644 index d9b2f1c7..00000000 Binary files a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/7-class-grades.png and /dev/null differ diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/8-themes.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/8-themes.jpg new file mode 100644 index 00000000..94788cd7 Binary files /dev/null and b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/8-themes.jpg differ diff --git a/app/src/main/play/listings/sk/full-description.txt b/app/src/main/play/listings/sk/full-description.txt new file mode 100644 index 00000000..23682598 --- /dev/null +++ b/app/src/main/play/listings/sk/full-description.txt @@ -0,0 +1,14 @@ +Aplikácia je určená pre užívateľov denníka VULCAN UONET+. + +Zvýraznené vlastnosti a funkcie: +- výpočet váženého priemeru, +- percentuálne zobrazenie dochádzky, +- šťastné číslo, +- náhľad na ďalšie a dokončené lekcie, +- tmavý motív, +- voliteľné reklamy, +- offline režim, +- upozornenia. + +GitHub: https://github.com/wulkanowy/wulkanowy +Discord: https://discord.gg/vccAQBr diff --git a/app/src/main/play/listings/sk/short-description.txt b/app/src/main/play/listings/sk/short-description.txt new file mode 100644 index 00000000..645ebbb6 --- /dev/null +++ b/app/src/main/play/listings/sk/short-description.txt @@ -0,0 +1 @@ +Neoficiálna aplikácia žiaka a rodiča pre denníka VULCAN UONET+ diff --git a/app/src/main/play/listings/sk/title.txt b/app/src/main/play/listings/sk/title.txt new file mode 100644 index 00000000..aa50ce77 --- /dev/null +++ b/app/src/main/play/listings/sk/title.txt @@ -0,0 +1 @@ +Wulkanowy Denníček diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 43be8682..e4fa5476 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,5 @@ -Wersja 0.17.2 +Wersja 2.6.7 -- naprawiliśmy wyświetlanie przycisku oznaczania zadania domowego jako wykonanego -- naprawiliśmy rzadki błąd ze stabilnością przy wysyłaniu wiadomości -- naprawiliśmy błąd po logowaniu w domyślnym trybie, jeśli wcześniej użytkownik zalogowany był w trybie Mobilnego API -- ulepszyliśmy wygląd okienka ze szczegółami błędu +— taka tam aktualizacja wiadomości w czwartek. Jeśli widzisz to w piątek, to może już nie działać Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases diff --git a/app/src/main/res/drawable-hdpi/ic_shortcut_attendance.png b/app/src/main/res/drawable-hdpi/ic_shortcut_attendance.png new file mode 100644 index 00000000..0b5feff2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_shortcut_attendance.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_shortcut_exam.png b/app/src/main/res/drawable-hdpi/ic_shortcut_exam.png new file mode 100644 index 00000000..e5af0d08 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_shortcut_exam.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_shortcut_grade.png b/app/src/main/res/drawable-hdpi/ic_shortcut_grade.png new file mode 100644 index 00000000..095a8228 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_shortcut_grade.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_shortcut_message.png b/app/src/main/res/drawable-hdpi/ic_shortcut_message.png new file mode 100644 index 00000000..7bcd79e0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_shortcut_message.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_shortcut_timetable.png b/app/src/main/res/drawable-hdpi/ic_shortcut_timetable.png new file mode 100644 index 00000000..2808559a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_shortcut_timetable.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_push.png b/app/src/main/res/drawable-hdpi/ic_stat_all.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_stat_push.png rename to app/src/main/res/drawable-hdpi/ic_stat_all.png diff --git a/app/src/main/res/drawable-hdpi/ic_stat_grade.png b/app/src/main/res/drawable-hdpi/ic_stat_grade.png deleted file mode 100644 index d97efb5d..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_luckynumber.png b/app/src/main/res/drawable-hdpi/ic_stat_luckynumber.png deleted file mode 100644 index b91c4ae6..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_message.png b/app/src/main/res/drawable-hdpi/ic_stat_message.png deleted file mode 100644 index ce0fdabc..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_note.png b/app/src/main/res/drawable-hdpi/ic_stat_note.png deleted file mode 100644 index 7318c419..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_shortcut_attendance.png b/app/src/main/res/drawable-mdpi/ic_shortcut_attendance.png new file mode 100644 index 00000000..e81e7ad9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_shortcut_attendance.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_shortcut_exam.png b/app/src/main/res/drawable-mdpi/ic_shortcut_exam.png new file mode 100644 index 00000000..3bdb5297 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_shortcut_exam.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_shortcut_grade.png b/app/src/main/res/drawable-mdpi/ic_shortcut_grade.png new file mode 100644 index 00000000..e3513507 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_shortcut_grade.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_shortcut_message.png b/app/src/main/res/drawable-mdpi/ic_shortcut_message.png new file mode 100644 index 00000000..392c45d2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_shortcut_message.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_shortcut_timetable.png b/app/src/main/res/drawable-mdpi/ic_shortcut_timetable.png new file mode 100644 index 00000000..7d61306a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_shortcut_timetable.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_push.png b/app/src/main/res/drawable-mdpi/ic_stat_all.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_stat_push.png rename to app/src/main/res/drawable-mdpi/ic_stat_all.png diff --git a/app/src/main/res/drawable-mdpi/ic_stat_grade.png b/app/src/main/res/drawable-mdpi/ic_stat_grade.png deleted file mode 100644 index c85b2702..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_luckynumber.png b/app/src/main/res/drawable-mdpi/ic_stat_luckynumber.png deleted file mode 100644 index bfced4eb..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_message.png b/app/src/main/res/drawable-mdpi/ic_stat_message.png deleted file mode 100644 index b357b321..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_note.png b/app/src/main/res/drawable-mdpi/ic_stat_note.png deleted file mode 100644 index 55a4fbbf..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/main/res/drawable-night/background_header_note.xml b/app/src/main/res/drawable-night/background_header_note.xml deleted file mode 100644 index 6b594e7c..00000000 --- a/app/src/main/res/drawable-night/background_header_note.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable-night/ic_all_divider.xml b/app/src/main/res/drawable-night/ic_all_divider.xml deleted file mode 100644 index cd444a28..00000000 --- a/app/src/main/res/drawable-night/ic_all_divider.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml b/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml new file mode 100644 index 00000000..dad56a17 --- /dev/null +++ b/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v23/img_splash_logo.png b/app/src/main/res/drawable-v23/img_splash_logo.png deleted file mode 100644 index 61489d81..00000000 Binary files a/app/src/main/res/drawable-v23/img_splash_logo.png and /dev/null differ diff --git a/app/src/main/res/drawable-v23/layer_splash_background.xml b/app/src/main/res/drawable-v23/layer_splash_background.xml deleted file mode 100644 index 1b4b64ec..00000000 --- a/app/src/main/res/drawable-v23/layer_splash_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable-xhdpi/ic_shortcut_attendance.png b/app/src/main/res/drawable-xhdpi/ic_shortcut_attendance.png new file mode 100644 index 00000000..302b9e0e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_shortcut_attendance.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_shortcut_exam.png b/app/src/main/res/drawable-xhdpi/ic_shortcut_exam.png new file mode 100644 index 00000000..9f36ca47 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_shortcut_exam.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_shortcut_grade.png b/app/src/main/res/drawable-xhdpi/ic_shortcut_grade.png new file mode 100644 index 00000000..281bc7a3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_shortcut_grade.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_shortcut_message.png b/app/src/main/res/drawable-xhdpi/ic_shortcut_message.png new file mode 100644 index 00000000..184929a3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_shortcut_message.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_shortcut_timetable.png b/app/src/main/res/drawable-xhdpi/ic_shortcut_timetable.png new file mode 100644 index 00000000..9a40fe61 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_shortcut_timetable.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_push.png b/app/src/main/res/drawable-xhdpi/ic_stat_all.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_stat_push.png rename to app/src/main/res/drawable-xhdpi/ic_stat_all.png diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_grade.png b/app/src/main/res/drawable-xhdpi/ic_stat_grade.png deleted file mode 100644 index c55ca7fd..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_luckynumber.png b/app/src/main/res/drawable-xhdpi/ic_stat_luckynumber.png deleted file mode 100644 index 49e502ac..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_message.png b/app/src/main/res/drawable-xhdpi/ic_stat_message.png deleted file mode 100644 index 0e67ade3..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_note.png b/app/src/main/res/drawable-xhdpi/ic_stat_note.png deleted file mode 100644 index ea674a79..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_shortcut_attendance.png b/app/src/main/res/drawable-xxhdpi/ic_shortcut_attendance.png new file mode 100644 index 00000000..9b4ef2da Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_shortcut_attendance.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_shortcut_exam.png b/app/src/main/res/drawable-xxhdpi/ic_shortcut_exam.png new file mode 100644 index 00000000..c2677a13 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_shortcut_exam.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_shortcut_grade.png b/app/src/main/res/drawable-xxhdpi/ic_shortcut_grade.png new file mode 100644 index 00000000..8b51021c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_shortcut_grade.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_shortcut_message.png b/app/src/main/res/drawable-xxhdpi/ic_shortcut_message.png new file mode 100644 index 00000000..250c290a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_shortcut_message.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_shortcut_timetable.png b/app/src/main/res/drawable-xxhdpi/ic_shortcut_timetable.png new file mode 100644 index 00000000..c530153a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_shortcut_timetable.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_push.png b/app/src/main/res/drawable-xxhdpi/ic_stat_all.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_stat_push.png rename to app/src/main/res/drawable-xxhdpi/ic_stat_all.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_grade.png b/app/src/main/res/drawable-xxhdpi/ic_stat_grade.png deleted file mode 100644 index eeb6fa41..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_grade.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_luckynumber.png b/app/src/main/res/drawable-xxhdpi/ic_stat_luckynumber.png deleted file mode 100644 index 9bab1373..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_luckynumber.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_message.png b/app/src/main/res/drawable-xxhdpi/ic_stat_message.png deleted file mode 100644 index 2e881824..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_message.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_note.png b/app/src/main/res/drawable-xxhdpi/ic_stat_note.png deleted file mode 100644 index 174e1509..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_note.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shortcut_attendance.png b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_attendance.png new file mode 100644 index 00000000..7b9a68a7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_attendance.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shortcut_exam.png b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_exam.png new file mode 100644 index 00000000..519c50ab Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_exam.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shortcut_grade.png b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_grade.png new file mode 100644 index 00000000..13c793d5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_grade.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shortcut_message.png b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_message.png new file mode 100644 index 00000000..eaffa2dd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_message.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shortcut_timetable.png b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_timetable.png new file mode 100644 index 00000000..03522e9a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shortcut_timetable.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_push.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_all.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_stat_push.png rename to app/src/main/res/drawable-xxxhdpi/ic_stat_all.png diff --git a/app/src/main/res/drawable/background_grade_details_rounded.xml b/app/src/main/res/drawable/background_grade_details_rounded.xml new file mode 100644 index 00000000..e24088a0 --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_grade_details_weight_rounded.xml b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml new file mode 100644 index 00000000..4b210912 --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_grade_rounded.xml b/app/src/main/res/drawable/background_grade_rounded.xml new file mode 100644 index 00000000..52c10c2f --- /dev/null +++ b/app/src/main/res/drawable/background_grade_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_grade_small_rounded.xml b/app/src/main/res/drawable/background_grade_small_rounded.xml new file mode 100644 index 00000000..dd50417f --- /dev/null +++ b/app/src/main/res/drawable/background_grade_small_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_header_note.xml b/app/src/main/res/drawable/background_header_note.xml index c21e55c6..8cf84a1c 100644 --- a/app/src/main/res/drawable/background_header_note.xml +++ b/app/src/main/res/drawable/background_header_note.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/background_luckynumber_widget.xml b/app/src/main/res/drawable/background_luckynumber_widget_button.xml similarity index 62% rename from app/src/main/res/drawable/background_luckynumber_widget.xml rename to app/src/main/res/drawable/background_luckynumber_widget_button.xml index f29744d0..66b1685f 100644 --- a/app/src/main/res/drawable/background_luckynumber_widget.xml +++ b/app/src/main/res/drawable/background_luckynumber_widget_button.xml @@ -1,6 +1,6 @@ - - + + diff --git a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml b/app/src/main/res/drawable/background_luckynumber_widget_dark.xml deleted file mode 100644 index fa15fd85..00000000 --- a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_material_alert_dialog.xml b/app/src/main/res/drawable/background_material_alert_dialog.xml new file mode 100644 index 00000000..5ab8a350 --- /dev/null +++ b/app/src/main/res/drawable/background_material_alert_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_timetable_time_left.xml b/app/src/main/res/drawable/background_timetable_time_left.xml new file mode 100644 index 00000000..0f332611 --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_time_left.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_timetable_widget_avatar.xml b/app/src/main/res/drawable/background_timetable_widget_avatar.xml new file mode 100644 index 00000000..48298d67 --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_widget_avatar.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_item_timetable.xml b/app/src/main/res/drawable/background_widget_item_timetable.xml new file mode 100644 index 00000000..510c70c0 --- /dev/null +++ b/app/src/main/res/drawable/background_widget_item_timetable.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_timetable.xml b/app/src/main/res/drawable/background_widget_timetable.xml new file mode 100644 index 00000000..b589ad29 --- /dev/null +++ b/app/src/main/res/drawable/background_widget_timetable.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_about_facebook.xml b/app/src/main/res/drawable/ic_about_facebook.xml new file mode 100644 index 00000000..a1b7b46e --- /dev/null +++ b/app/src/main/res/drawable/ic_about_facebook.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_about_twitter.xml b/app/src/main/res/drawable/ic_about_twitter.xml new file mode 100644 index 00000000..7b7cf6d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_about_twitter.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_account_details_family.xml b/app/src/main/res/drawable/ic_account_details_family.xml new file mode 100644 index 00000000..363b4f58 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_details_family.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_all_about.xml b/app/src/main/res/drawable/ic_all_about.xml index 3dfbbda1..3868f85c 100644 --- a/app/src/main/res/drawable/ic_all_about.xml +++ b/app/src/main/res/drawable/ic_all_about.xml @@ -2,6 +2,7 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" + android:tint="?colorOnSurface" android:viewportHeight="24"> + + + diff --git a/app/src/main/res/drawable/ic_all_divider.xml b/app/src/main/res/drawable/ic_all_divider.xml index 61e96497..922882c2 100644 --- a/app/src/main/res/drawable/ic_all_divider.xml +++ b/app/src/main/res/drawable/ic_all_divider.xml @@ -1,9 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_about_homepage.xml b/app/src/main/res/drawable/ic_all_home.xml similarity index 100% rename from app/src/main/res/drawable/ic_about_homepage.xml rename to app/src/main/res/drawable/ic_all_home.xml diff --git a/app/src/main/res/drawable/ic_school_phone.xml b/app/src/main/res/drawable/ic_all_phone.xml similarity index 100% rename from app/src/main/res/drawable/ic_school_phone.xml rename to app/src/main/res/drawable/ic_all_phone.xml diff --git a/app/src/main/res/drawable/ic_all_round_check.xml b/app/src/main/res/drawable/ic_all_round_check.xml new file mode 100644 index 00000000..aecaf234 --- /dev/null +++ b/app/src/main/res/drawable/ic_all_round_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_auth_success.xml b/app/src/main/res/drawable/ic_auth_success.xml new file mode 100644 index 00000000..015553c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_auth_success.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar_all.xml b/app/src/main/res/drawable/ic_calendar_all.xml new file mode 100644 index 00000000..5908035e --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_all.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_chevron_left.xml b/app/src/main/res/drawable/ic_chevron_left.xml index ee3ff4be..4250fae4 100644 --- a/app/src/main/res/drawable/ic_chevron_left.xml +++ b/app/src/main/res/drawable/ic_chevron_left.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml index a6d73497..de5037cf 100644 --- a/app/src/main/res/drawable/ic_chevron_right.xml +++ b/app/src/main/res/drawable/ic_chevron_right.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml new file mode 100644 index 00000000..d2932fe6 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_circle_notification.xml b/app/src/main/res/drawable/ic_circle_notification.xml new file mode 100644 index 00000000..6059212c --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_notification.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..1d6c0046 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_dashboard_warning.xml b/app/src/main/res/drawable/ic_dashboard_warning.xml new file mode 100644 index 00000000..e7a5dc5a --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_warning.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_error_filled.xml b/app/src/main/res/drawable/ic_error_filled.xml new file mode 100644 index 00000000..61b575dc --- /dev/null +++ b/app/src/main/res/drawable/ic_error_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_fullscreen.xml b/app/src/main/res/drawable/ic_fullscreen.xml new file mode 100644 index 00000000..86b7649b --- /dev/null +++ b/app/src/main/res/drawable/ic_fullscreen.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_fullscreen_exit.xml b/app/src/main/res/drawable/ic_fullscreen_exit.xml new file mode 100644 index 00000000..bb7140f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_fullscreen_exit.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 00000000..9c6ba292 --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..f20e2094 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml new file mode 100644 index 00000000..b1b01a0b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml new file mode 100644 index 00000000..e2e74731 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_login.xml b/app/src/main/res/drawable/ic_login.xml new file mode 100644 index 00000000..57aef3dc --- /dev/null +++ b/app/src/main/res/drawable/ic_login.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_main_dashboard.xml b/app/src/main/res/drawable/ic_main_dashboard.xml new file mode 100644 index 00000000..ed226925 --- /dev/null +++ b/app/src/main/res/drawable/ic_main_dashboard.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_main_timetable.xml b/app/src/main/res/drawable/ic_main_timetable.xml index b8f5cb85..e33aae16 100644 --- a/app/src/main/res/drawable/ic_main_timetable.xml +++ b/app/src/main/res/drawable/ic_main_timetable.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" /> diff --git a/app/src/main/res/drawable/ic_menu_attendance_calculator.xml b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml new file mode 100644 index 00000000..8a7d209a --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_message_delete_forever.xml b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml new file mode 100644 index 00000000..a7b5ac53 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_message_print.xml b/app/src/main/res/drawable/ic_menu_message_print.xml new file mode 100644 index 00000000..204b0f6e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_print.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_message_restore.xml b/app/src/main/res/drawable/ic_menu_message_restore.xml new file mode 100644 index 00000000..5c8544f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_restore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_message_share.xml b/app/src/main/res/drawable/ic_menu_message_share.xml new file mode 100644 index 00000000..67a8ee49 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_order_drag.xml b/app/src/main/res/drawable/ic_menu_order_drag.xml new file mode 100644 index 00000000..623c67d2 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_order_drag.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml new file mode 100644 index 00000000..ddb6d2f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_message_select_all.xml b/app/src/main/res/drawable/ic_message_select_all.xml new file mode 100644 index 00000000..eab195d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_select_all.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_message_unselect_all.xml b/app/src/main/res/drawable/ic_message_unselect_all.xml new file mode 100644 index 00000000..c388522e --- /dev/null +++ b/app/src/main/res/drawable/ic_message_unselect_all.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more_conferences.xml b/app/src/main/res/drawable/ic_more_conferences.xml new file mode 100644 index 00000000..65d6b6e8 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_conferences.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_off.xml b/app/src/main/res/drawable/ic_notifications_off.xml new file mode 100644 index 00000000..094ed75f --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_off.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_scale_balance.xml b/app/src/main/res/drawable/ic_scale_balance.xml new file mode 100644 index 00000000..c65467a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_scale_balance.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..cd9985cb --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_ads.xml b/app/src/main/res/drawable/ic_settings_ads.xml new file mode 100644 index 00000000..c333ea76 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_ads.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_settings_advanced.xml b/app/src/main/res/drawable/ic_settings_advanced.xml new file mode 100644 index 00000000..2fd7e590 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_advanced.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_settings_appearance.xml b/app/src/main/res/drawable/ic_settings_appearance.xml new file mode 100644 index 00000000..afea27f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_appearance.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_settings_notifications.xml b/app/src/main/res/drawable/ic_settings_notifications.xml new file mode 100644 index 00000000..f4ff247f --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_notifications.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_sync.xml b/app/src/main/res/drawable/ic_settings_sync.xml new file mode 100644 index 00000000..3697ac0b --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_sync.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_splash_logo.xml b/app/src/main/res/drawable/ic_splash_logo.xml new file mode 100644 index 00000000..e2e74731 --- /dev/null +++ b/app/src/main/res/drawable/ic_splash_logo.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_grade.xml b/app/src/main/res/drawable/ic_stat_grade.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v24/ic_stat_grade.xml rename to app/src/main/res/drawable/ic_stat_grade.xml diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_luckynumber.xml b/app/src/main/res/drawable/ic_stat_luckynumber.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v24/ic_stat_luckynumber.xml rename to app/src/main/res/drawable/ic_stat_luckynumber.xml diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_message.xml b/app/src/main/res/drawable/ic_stat_message.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v24/ic_stat_message.xml rename to app/src/main/res/drawable/ic_stat_message.xml diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_note.xml b/app/src/main/res/drawable/ic_stat_note.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v24/ic_stat_note.xml rename to app/src/main/res/drawable/ic_stat_note.xml diff --git a/app/src/main/res/drawable/ic_stat_timetable.xml b/app/src/main/res/drawable/ic_stat_timetable.xml new file mode 100644 index 00000000..9efc0348 --- /dev/null +++ b/app/src/main/res/drawable/ic_stat_timetable.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_timetable_widget_swap.xml b/app/src/main/res/drawable/ic_timetable_widget_swap.xml new file mode 100644 index 00000000..09c50b4f --- /dev/null +++ b/app/src/main/res/drawable/ic_timetable_widget_swap.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_widget_chevron.png b/app/src/main/res/drawable/ic_widget_chevron.png deleted file mode 100644 index 34345521..00000000 Binary files a/app/src/main/res/drawable/ic_widget_chevron.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_widget_chevron.xml b/app/src/main/res/drawable/ic_widget_chevron.xml new file mode 100644 index 00000000..871aca9b --- /dev/null +++ b/app/src/main/res/drawable/ic_widget_chevron.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/img_luckynumber_widget_preview.png b/app/src/main/res/drawable/img_luckynumber_widget_preview.png index 539b0a59..26701855 100644 Binary files a/app/src/main/res/drawable/img_luckynumber_widget_preview.png and b/app/src/main/res/drawable/img_luckynumber_widget_preview.png differ diff --git a/app/src/main/res/drawable/img_splash_logo.png b/app/src/main/res/drawable/img_splash_logo.png deleted file mode 100644 index fb521bf6..00000000 Binary files a/app/src/main/res/drawable/img_splash_logo.png and /dev/null differ diff --git a/app/src/main/res/drawable/img_timetable_widget_preview.png b/app/src/main/res/drawable/img_timetable_widget_preview.png index 55026025..5c1752cb 100644 Binary files a/app/src/main/res/drawable/img_timetable_widget_preview.png and b/app/src/main/res/drawable/img_timetable_widget_preview.png differ diff --git a/app/src/main/res/drawable/layer_splash_background.xml b/app/src/main/res/drawable/layer_splash_background.xml deleted file mode 100644 index 2cf46d1d..00000000 --- a/app/src/main/res/drawable/layer_splash_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/shape_badge.xml b/app/src/main/res/drawable/shape_badge.xml new file mode 100644 index 00000000..3153f592 --- /dev/null +++ b/app/src/main/res/drawable/shape_badge.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-v31/widget_timetable_preview.xml b/app/src/main/res/layout-v31/widget_timetable_preview.xml new file mode 100644 index 00000000..2c47836c --- /dev/null +++ b/app/src/main/res/layout-v31/widget_timetable_preview.xml @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 3841b25c..91279263 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,17 +1,20 @@ + android:layout_height="wrap_content" + android:background="@android:color/transparent" /> - + android:layout_height="0dp" + android:layout_weight="1" + tools:layout="@layout/fragment_login_form" /> + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d07dbbd8..a9284234 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,25 +1,44 @@ - - - - - - - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml index f59dcabd..e50cf6b3 100644 --- a/app/src/main/res/layout/activity_send_message.xml +++ b/app/src/main/res/layout/activity_send_message.xml @@ -7,31 +7,30 @@ android:layout_height="match_parent" android:orientation="vertical"> - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + app:layout_constraintTop_toBottomOf="@id/send_app_bar"> + android:layout_height="wrap_content"> + + - + app:layout_constraintTop_toBottomOf="@id/send_app_bar" /> diff --git a/app/src/main/res/layout/activity_widget_configure.xml b/app/src/main/res/layout/activity_widget_configure.xml index 548b8604..9a463284 100644 --- a/app/src/main/res/layout/activity_widget_configure.xml +++ b/app/src/main/res/layout/activity_widget_configure.xml @@ -10,9 +10,7 @@ android:layout_width="match_parent" android:layout_height="64dp" android:paddingStart="24dp" - android:paddingLeft="24dp" android:paddingEnd="24dp" - android:paddingRight="24dp" android:text="@string/account_title" android:textSize="20sp" android:textStyle="bold" diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account.xml deleted file mode 100644 index f2ee0bca..00000000 --- a/app/src/main/res/layout/dialog_account.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_account_edit.xml b/app/src/main/res/layout/dialog_account_edit.xml new file mode 100644 index 00000000..ec5f93a6 --- /dev/null +++ b/app/src/main/res/layout/dialog_account_edit.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_account_quick.xml b/app/src/main/res/layout/dialog_account_quick.xml new file mode 100644 index 00000000..d8be4842 --- /dev/null +++ b/app/src/main/res/layout/dialog_account_quick.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_additional_add.xml b/app/src/main/res/layout/dialog_additional_add.xml new file mode 100644 index 00000000..c042afd4 --- /dev/null +++ b/app/src/main/res/layout/dialog_additional_add.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_attendance.xml b/app/src/main/res/layout/dialog_attendance.xml index 22268aac..b9c63539 100644 --- a/app/src/main/res/layout/dialog_attendance.xml +++ b/app/src/main/res/layout/dialog_attendance.xml @@ -1,92 +1,167 @@ - + android:paddingStart="24dp" + android:paddingEnd="8dp"> + + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogSubjectTitle" /> + + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogSubjectValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogDescriptionTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogDescriptionValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogDateTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogDateValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogNumberTitle" /> - + android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="24dp" + android:insetLeft="0dp" + android:insetTop="0dp" + android:insetRight="0dp" + android:insetBottom="0dp" + android:minHeight="36dp" + android:text="@string/all_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/attendanceDialogNumberValue" /> + diff --git a/app/src/main/res/layout/dialog_auth.xml b/app/src/main/res/layout/dialog_auth.xml new file mode 100644 index 00000000..a0b9d6ea --- /dev/null +++ b/app/src/main/res/layout/dialog_auth.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml new file mode 100644 index 00000000..019d8932 --- /dev/null +++ b/app/src/main/res/layout/dialog_captcha.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_conference.xml b/app/src/main/res/layout/dialog_conference.xml new file mode 100644 index 00000000..3d33fd56 --- /dev/null +++ b/app/src/main/res/layout/dialog_conference.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_error.xml b/app/src/main/res/layout/dialog_error.xml index ac2c542a..98b9c8b1 100644 --- a/app/src/main/res/layout/dialog_error.xml +++ b/app/src/main/res/layout/dialog_error.xml @@ -4,18 +4,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minWidth="300dp" - android:orientation="vertical"> + android:orientation="vertical" + tools:context=".ui.base.ErrorDialog"> - - - - - - - - - - - - - - + android:layout_marginBottom="5dp" + android:paddingHorizontal="20dp" + android:paddingTop="10dp" + android:textColor="?android:textColorSecondary" + android:textIsSelectable="true" + tools:text="@tools:sample/lorem" /> - + - + - - - - + + + diff --git a/app/src/main/res/layout/dialog_exam.xml b/app/src/main/res/layout/dialog_exam.xml index 8e46dc4f..e2f77e1f 100644 --- a/app/src/main/res/layout/dialog_exam.xml +++ b/app/src/main/res/layout/dialog_exam.xml @@ -1,113 +1,255 @@ - + android:paddingStart="8dp" + android:paddingEnd="8dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogSubjectTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogSubjectValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogTypeTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogTypeValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogTeacherTitle" /> + + + + + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogDeadlineDateValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogEntryDateTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogEntryDateValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogDescriptionTitle" /> + + - + android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="24dp" + android:insetLeft="0dp" + android:insetTop="0dp" + android:insetRight="0dp" + android:insetBottom="0dp" + android:minHeight="36dp" + android:text="@string/all_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/examDialogDescriptionValue" /> + diff --git a/app/src/main/res/layout/dialog_excuse.xml b/app/src/main/res/layout/dialog_excuse.xml index 4ed9294c..8297bc14 100644 --- a/app/src/main/res/layout/dialog_excuse.xml +++ b/app/src/main/res/layout/dialog_excuse.xml @@ -1,20 +1,26 @@ + android:layout_height="wrap_content" + app:counterEnabled="true" + app:counterMaxLength="256"> + android:layout_weight="1" + android:hint="@string/attendance_excuse_dialog_reason" + android:maxLength="256" /> diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index be0570c2..8606a5ce 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -8,19 +8,20 @@ + android:minWidth="300dp" + android:paddingStart="24dp" + android:paddingEnd="8dp"> - + android:layout_marginEnd="16dp" + android:background="@drawable/background_grade_details_weight_rounded" + android:backgroundTint="@color/grade_black" + android:gravity="center_horizontal"> + + + + + + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> - - - - - - - - - - - - + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml index 7c149d4b..10b71907 100644 --- a/app/src/main/res/layout/dialog_homework.xml +++ b/app/src/main/res/layout/dialog_homework.xml @@ -1,47 +1,58 @@ - + android:layout_height="wrap_content"> - + app:layout_constraintTop_toBottomOf="@id/homeworkDialogRecycler" /> - + - - - + + diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml new file mode 100644 index 00000000..dc7ae32d --- /dev/null +++ b/app/src/main/res/layout/dialog_homework_add.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml index 31ca7dd3..fc32a252 100644 --- a/app/src/main/res/layout/dialog_lesson_completed.xml +++ b/app/src/main/res/layout/dialog_lesson_completed.xml @@ -1,145 +1,230 @@ + - + android:paddingStart="24dp" + android:paddingEnd="8dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogSubjectTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogSubjectValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogTopicTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogTopicValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogTeacherTitle" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogTeacherValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogChangesTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?colorTimetableCanceled" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogChangesValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogAbsenceTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogAbsenceValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogResourcesTitle" /> + android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="24dp" + android:insetLeft="0dp" + android:insetTop="0dp" + android:insetRight="0dp" + android:insetBottom="0dp" + android:minHeight="36dp" + android:text="@string/all_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/completedLessonDialogResourcesValue" /> - + diff --git a/app/src/main/res/layout/dialog_login_support.xml b/app/src/main/res/layout/dialog_login_support.xml new file mode 100644 index 00000000..c04d7812 --- /dev/null +++ b/app/src/main/res/layout/dialog_login_support.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_mailbox_chooser.xml b/app/src/main/res/layout/dialog_mailbox_chooser.xml new file mode 100644 index 00000000..5c93acb5 --- /dev/null +++ b/app/src/main/res/layout/dialog_mailbox_chooser.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_mobile_device.xml b/app/src/main/res/layout/dialog_mobile_device.xml index 7b128605..c526ed74 100644 --- a/app/src/main/res/layout/dialog_mobile_device.xml +++ b/app/src/main/res/layout/dialog_mobile_device.xml @@ -1,98 +1,151 @@ - + android:orientation="vertical"> - + android:layout_marginTop="24dp" + android:adjustViewBounds="true" + android:contentDescription="@string/mobile_device_qr" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@tools:sample/avatars" /> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + + diff --git a/app/src/main/res/layout/dialog_note.xml b/app/src/main/res/layout/dialog_note.xml index 71506b01..3b88ea5f 100644 --- a/app/src/main/res/layout/dialog_note.xml +++ b/app/src/main/res/layout/dialog_note.xml @@ -1,109 +1,197 @@ + - + android:paddingStart="24dp" + android:paddingEnd="8dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogCategoryTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogCategoryValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogTeacherTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogTeacherValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogDateTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogDateValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogPointsTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogPointsValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogContentTitle" /> - + android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="24dp" + android:insetLeft="0dp" + android:insetTop="0dp" + android:insetRight="0dp" + android:insetBottom="0dp" + android:minHeight="36dp" + android:text="@string/all_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/noteDialogContentValue" /> + diff --git a/app/src/main/res/layout/dialog_school_announcement.xml b/app/src/main/res/layout/dialog_school_announcement.xml new file mode 100644 index 00000000..a771b772 --- /dev/null +++ b/app/src/main/res/layout/dialog_school_announcement.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_timetable.xml b/app/src/main/res/layout/dialog_timetable.xml index f3d4cf1c..de269648 100644 --- a/app/src/main/res/layout/dialog_timetable.xml +++ b/app/src/main/res/layout/dialog_timetable.xml @@ -1,167 +1,280 @@ + android:layout_height="match_parent"> - + android:paddingStart="24dp" + android:paddingEnd="8dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogChangesTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogChangesValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogLessonTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogLessonNewValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogTeacherTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogTeacherNewValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogGroupTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogGroupValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogRoomTitle" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogRoomNewValue" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogTimeTitle" /> - + android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="24dp" + android:insetLeft="0dp" + android:insetTop="0dp" + android:insetRight="0dp" + android:insetBottom="0dp" + android:minHeight="36dp" + android:text="@string/all_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/timetableDialogTimeValue" /> + diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 556bdfd0..6747b7d0 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,10 +1,12 @@ + android:layout_height="match_parent" + tools:listitem="@layout/item_about" /> diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml new file mode 100644 index 00000000..b7873833 --- /dev/null +++ b/app/src/main/res/layout/fragment_account.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_account_details.xml b/app/src/main/res/layout/fragment_account_details.xml new file mode 100644 index 00000000..0e42348d --- /dev/null +++ b/app/src/main/res/layout/fragment_account_details.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml index cd8b6036..078daf61 100644 --- a/app/src/main/res/layout/fragment_attendance.xml +++ b/app/src/main/res/layout/fragment_attendance.xml @@ -1,18 +1,18 @@ - + android:layout_height="0dp" + android:layout_weight="1"> - @@ -106,7 +105,7 @@ + android:visibility="gone" + tools:ignore="UnusedAttribute" + tools:visibility="visible"> + app:srcCompat="@drawable/ic_chevron_left" + app:tint="?colorPrimary" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_right" + app:tint="?colorPrimary" /> - + diff --git a/app/src/main/res/layout/fragment_attendance_calculator.xml b/app/src/main/res/layout/fragment_attendance_calculator.xml new file mode 100644 index 00000000..346c6aec --- /dev/null +++ b/app/src/main/res/layout/fragment_attendance_calculator.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_attendance_summary.xml b/app/src/main/res/layout/fragment_attendance_summary.xml index da0ce7f2..6b92e23f 100644 --- a/app/src/main/res/layout/fragment_attendance_summary.xml +++ b/app/src/main/res/layout/fragment_attendance_summary.xml @@ -49,9 +49,8 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_creator.xml b/app/src/main/res/layout/fragment_contributor.xml similarity index 80% rename from app/src/main/res/layout/fragment_creator.xml rename to app/src/main/res/layout/fragment_contributor.xml index 399ab599..d1902d88 100644 --- a/app/src/main/res/layout/fragment_creator.xml +++ b/app/src/main/res/layout/fragment_contributor.xml @@ -1,11 +1,11 @@ - + android:layout_weight="1" + tools:listitem="@layout/item_contributor" /> diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 00000000..9de44a70 --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_debug.xml b/app/src/main/res/layout/fragment_debug.xml new file mode 100644 index 00000000..2d71d89c --- /dev/null +++ b/app/src/main/res/layout/fragment_debug.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_debug_notifications.xml b/app/src/main/res/layout/fragment_debug_notifications.xml new file mode 100644 index 00000000..372e1055 --- /dev/null +++ b/app/src/main/res/layout/fragment_debug_notifications.xml @@ -0,0 +1,6 @@ + diff --git a/app/src/main/res/layout/fragment_exam.xml b/app/src/main/res/layout/fragment_exam.xml index 1e1b0c99..8dba5361 100644 --- a/app/src/main/res/layout/fragment_exam.xml +++ b/app/src/main/res/layout/fragment_exam.xml @@ -10,9 +10,8 @@ android:layout_height="match_parent" android:layout_marginBottom="50dp"> - + app:srcCompat="@drawable/ic_chevron_left" + app:tint="?colorPrimary" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_right" + app:tint="?colorPrimary" /> diff --git a/app/src/main/res/layout/fragment_grade.xml b/app/src/main/res/layout/fragment_grade.xml index 0bc864b2..f76419d1 100644 --- a/app/src/main/res/layout/fragment_grade.xml +++ b/app/src/main/res/layout/fragment_grade.xml @@ -11,13 +11,14 @@ android:layout_height="48dp" android:background="?colorSurface" android:visibility="invisible" + android:outlineProvider="none" app:tabMode="scrollable" app:tabSelectedTextColor="?colorPrimary" app:tabTextColor="@color/material_on_surface_emphasis_medium" tools:ignore="UnusedAttribute" tools:visibility="visible" /> - - - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:orientation="vertical" + tools:ignore="UselessParent"> - - + android:background="?android:windowBackground" + android:padding="5dp" + android:visibility="visible" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="UnusedAttribute" + tools:listitem="@layout/item_attendance_summary" + tools:visibility="visible"> - - - - - + android:ellipsize="middle" + android:paddingStart="10dp" + android:paddingLeft="10dp" + android:paddingTop="10dp" + android:paddingEnd="30dp" + android:paddingRight="30dp" + android:paddingBottom="10dp" + android:spinnerMode="dialog" /> + - + + + + + android:indeterminate="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="gone" /> - - - - - - - - - - - + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/item_grade_statistics_pie" + tools:visibility="visible" /> - + + - - - - - - - - - - - - - - - - + android:layout_height="wrap_content"> + android:orientation="vertical" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="UseCompoundDrawables" + tools:visibility="gone"> - + + - - + android:layout_marginTop="20dp" + android:gravity="center" + android:text="@string/grade_no_items" + android:textSize="20sp" /> - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_grade_summary.xml b/app/src/main/res/layout/fragment_grade_summary.xml index 3f21e61c..de2d0f3b 100644 --- a/app/src/main/res/layout/fragment_grade_summary.xml +++ b/app/src/main/res/layout/fragment_grade_summary.xml @@ -17,9 +17,8 @@ tools:listitem="@layout/item_grade_summary" /> - - @@ -92,7 +93,7 @@ + + + app:srcCompat="@drawable/ic_chevron_left" + app:tint="?colorPrimary" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_right" + app:tint="?colorPrimary" /> diff --git a/app/src/main/res/layout/fragment_license.xml b/app/src/main/res/layout/fragment_license.xml index 80ecd6b1..5551d4f1 100644 --- a/app/src/main/res/layout/fragment_license.xml +++ b/app/src/main/res/layout/fragment_license.xml @@ -1,11 +1,11 @@ - + android:layout_height="match_parent" + tools:listitem="@layout/item_license" /> diff --git a/app/src/main/res/layout/fragment_login_advanced.xml b/app/src/main/res/layout/fragment_login_advanced.xml index 55bc62ae..37551dec 100644 --- a/app/src/main/res/layout/fragment_login_advanced.xml +++ b/app/src/main/res/layout/fragment_login_advanced.xml @@ -5,9 +5,8 @@ android:layout_height="match_parent" tools:context=".ui.modules.login.advanced.LoginAdvancedFragment"> - @@ -127,7 +127,7 @@ @@ -184,9 +185,34 @@ tools:ignore="Deprecated,LabelFor" /> + + + + + + app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout"> - + + + + + app:layout_constraintTop_toBottomOf="@+id/login_form_message" + app:layout_constraintVertical_bias="0" + app:layout_goneMarginTop="64dp" /> + + app:layout_constraintTop_toBottomOf="@+id/loginFormErrorBox" + app:layout_goneMarginTop="48dp"> @@ -143,7 +182,7 @@ + app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout"> - @@ -215,13 +250,39 @@ tools:ignore="Deprecated,LabelFor" /> + + + + + + app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout" /> - + android:paddingBottom="16dp"> @@ -96,7 +94,7 @@ + tools:visibility="gone"> - + android:layout_marginTop="16dp"> + android:text="@string/all_details" + app:layout_constraintEnd_toStartOf="@id/loginRecoverErrorRetry" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:text="@string/all_retry" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/loginRecoverErrorDetails" + app:layout_constraintTop_toTopOf="parent" /> + + tools:visibility="gone"> - diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index 64e06cbd..6b603c9f 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -3,91 +3,25 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> - - + android:orientation="vertical" + tools:context=".ui.modules.login.studentselect.LoginStudentSelectFragment"> - - - - - - - - - - - - - - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:visibility="visible" /> + tools:itemCount="33" + tools:listitem="@layout/item_login_student_select_student" /> + + diff --git a/app/src/main/res/layout/fragment_login_symbol.xml b/app/src/main/res/layout/fragment_login_symbol.xml index 77f44d21..612e8c21 100644 --- a/app/src/main/res/layout/fragment_login_symbol.xml +++ b/app/src/main/res/layout/fragment_login_symbol.xml @@ -5,9 +5,8 @@ android:layout_height="match_parent" tools:context=".ui.modules.login.symbol.LoginSymbolFragment"> - + + + app:layout_constraintTop_toBottomOf="@+id/loginSymbolHelper" + app:placeholderText="@string/login_symbol_placeholder" + app:placeholderTextColor="?colorTertiary"> + tools:context=".ui.modules.debug.logviewer.LogViewerFragment"> - + app:srcCompat="@drawable/ic_refresh" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/fragment_lucky_number.xml b/app/src/main/res/layout/fragment_lucky_number.xml index a6b9b822..7c48523b 100644 --- a/app/src/main/res/layout/fragment_lucky_number.xml +++ b/app/src/main/res/layout/fragment_lucky_number.xml @@ -5,9 +5,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_menu_order.xml b/app/src/main/res/layout/fragment_menu_order.xml new file mode 100644 index 00000000..1773b45a --- /dev/null +++ b/app/src/main/res/layout/fragment_menu_order.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_message.xml b/app/src/main/res/layout/fragment_message.xml index b4cc0c2f..b3dac0dd 100644 --- a/app/src/main/res/layout/fragment_message.xml +++ b/app/src/main/res/layout/fragment_message.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + tools:listitem="@layout/item_message_preview" + tools:visibility="visible" /> - - - + app:srcCompat="@drawable/ic_all_add" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/fragment_more.xml b/app/src/main/res/layout/fragment_more.xml index dbd5470c..f0b0285f 100644 --- a/app/src/main/res/layout/fragment_more.xml +++ b/app/src/main/res/layout/fragment_more.xml @@ -1,10 +1,12 @@ + android:layout_height="match_parent" + tools:listitem="@layout/item_more" /> diff --git a/app/src/main/res/layout/fragment_note.xml b/app/src/main/res/layout/fragment_note.xml index e55f0007..dc3b9ef2 100644 --- a/app/src/main/res/layout/fragment_note.xml +++ b/app/src/main/res/layout/fragment_note.xml @@ -4,9 +4,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_height="match_parent" + tools:itemCount="4" + tools:listitem="@layout/item_note" /> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_notifications_center.xml b/app/src/main/res/layout/fragment_notifications_center.xml new file mode 100644 index 00000000..ba2f0ff4 --- /dev/null +++ b/app/src/main/res/layout/fragment_notifications_center.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_school.xml b/app/src/main/res/layout/fragment_school.xml index a7e9a213..a7c3551d 100644 --- a/app/src/main/res/layout/fragment_school.xml +++ b/app/src/main/res/layout/fragment_school.xml @@ -5,9 +5,8 @@ android:layout_height="match_parent" tools:context=".ui.modules.schoolandteachers.school.SchoolFragment"> - + android:layout_height="match_parent"> + android:orientation="vertical" + android:paddingVertical="8dp" + android:paddingStart="8dp" + android:paddingEnd="12dp" + android:visibility="invisible" + tools:visibility="visible"> + + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/school_address_button" + android:padding="4dp" + app:srcCompat="@drawable/ic_school_directions" + app:tint="?colorPrimary" /> + + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/school_telephone_button" + android:padding="4dp" + app:srcCompat="@drawable/ic_all_phone" + app:tint="?colorPrimary" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_schoolandteachers.xml b/app/src/main/res/layout/fragment_schoolandteachers.xml index 6414abb9..ae8c6c20 100644 --- a/app/src/main/res/layout/fragment_schoolandteachers.xml +++ b/app/src/main/res/layout/fragment_schoolandteachers.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_teacher.xml b/app/src/main/res/layout/fragment_teacher.xml index 484bf065..1253e312 100644 --- a/app/src/main/res/layout/fragment_teacher.xml +++ b/app/src/main/res/layout/fragment_teacher.xml @@ -4,9 +4,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + android:indeterminate="true" + tools:visibility="gone" /> + tools:listitem="@layout/item_timetable" + tools:visibility="visible" /> + + + android:visibility="gone" + tools:ignore="UnusedAttribute" + tools:visibility="visible"> + app:srcCompat="@drawable/ic_chevron_left" + app:tint="?colorPrimary" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:srcCompat="@drawable/ic_chevron_right" + app:tint="?colorPrimary" /> diff --git a/app/src/main/res/layout/fragment_timetable_additional.xml b/app/src/main/res/layout/fragment_timetable_additional.xml new file mode 100644 index 00000000..e50fd6b5 --- /dev/null +++ b/app/src/main/res/layout/fragment_timetable_additional.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_timetable_completed.xml b/app/src/main/res/layout/fragment_timetable_completed.xml index 11067044..8d647ff6 100644 --- a/app/src/main/res/layout/fragment_timetable_completed.xml +++ b/app/src/main/res/layout/fragment_timetable_completed.xml @@ -10,9 +10,8 @@ android:layout_height="match_parent" android:layout_marginBottom="50dp"> - + app:tint="?colorPrimary" /> + android:textSize="16sp" + tools:text="@tools:sample/date/ddmmyy" /> + app:tint="?colorPrimary" /> diff --git a/app/src/main/res/layout/header_account.xml b/app/src/main/res/layout/header_account.xml new file mode 100644 index 00000000..86252552 --- /dev/null +++ b/app/src/main/res/layout/header_account.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/header_exam.xml b/app/src/main/res/layout/header_exam.xml index 3a2e38c5..43f089f7 100644 --- a/app/src/main/res/layout/header_exam.xml +++ b/app/src/main/res/layout/header_exam.xml @@ -10,7 +10,7 @@ android:paddingTop="10dp" android:paddingRight="20dp" android:paddingBottom="10dp" - tools:context=".ui.modules.exam.ExamHeader"> + tools:context=".ui.modules.exam.ExamAdapter"> diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml index 75e01745..1765d9d1 100644 --- a/app/src/main/res/layout/header_grade_details.xml +++ b/app/src/main/res/layout/header_grade_details.xml @@ -1,81 +1,125 @@ - + android:background="?android:windowBackground"> - + android:background="?selectableItemBackground" + tools:context=".ui.modules.grade.details.GradeDetailsAdapter"> - + - + - + - - + + + + + + + + + diff --git a/app/src/main/res/layout/header_homework.xml b/app/src/main/res/layout/header_homework.xml index 207fcdb4..e1c6608f 100644 --- a/app/src/main/res/layout/header_homework.xml +++ b/app/src/main/res/layout/header_homework.xml @@ -10,7 +10,7 @@ android:paddingTop="10dp" android:paddingRight="20dp" android:paddingBottom="10dp" - tools:context=".ui.modules.homework.HomeworkHeader"> + tools:context=".ui.modules.homework.HomeworkAdapter"> diff --git a/app/src/main/res/layout/item_about.xml b/app/src/main/res/layout/item_about.xml index b5dc3758..6a95e552 100644 --- a/app/src/main/res/layout/item_about.xml +++ b/app/src/main/res/layout/item_about.xml @@ -4,17 +4,16 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="72dp" - android:background="?selectableItemBackground"> + android:background="?selectableItemBackground" + tools:context=".ui.modules.about.AboutAdapter"> @@ -24,9 +23,7 @@ android:layout_width="wrap_content" android:layout_height="32dp" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:layout_toEndOf="@id/aboutItemImage" - android:layout_toRightOf="@id/aboutItemImage" android:ellipsize="end" android:gravity="bottom" android:maxLines="1" @@ -36,16 +33,15 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 309be812..563e6b1e 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -1,53 +1,82 @@ - + android:paddingHorizontal="16dp" + android:paddingVertical="8dp" + tools:context=".ui.modules.account.AccountAdapter"> + tools:src="@tools:sample/avatars" /> + + - + + + diff --git a/app/src/main/res/layout/item_account_edit_color.xml b/app/src/main/res/layout/item_account_edit_color.xml new file mode 100644 index 00000000..c701f28d --- /dev/null +++ b/app/src/main/res/layout/item_account_edit_color.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/app/src/main/res/layout/item_attendance.xml b/app/src/main/res/layout/item_attendance.xml index 56bcab44..f3015283 100644 --- a/app/src/main/res/layout/item_attendance.xml +++ b/app/src/main/res/layout/item_attendance.xml @@ -1,4 +1,4 @@ - + tools:context=".ui.modules.attendance.AttendanceAdapter"> + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="@+id/attendanceItemDetailsContainer" + app:layout_constraintEnd_toStartOf="@+id/attendanceItemDetailsContainer" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/attendanceItemDetailsContainer"> + tools:text="5" + tools:visibility="visible" /> + app:tint="?attr/colorOnSurface" + tools:ignore="ContentDescription" /> - + android:layout_marginEnd="10dp" + app:layout_constraintEnd_toStartOf="@+id/attendanceItemAlert" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/attendanceItemNumberContainer" + app:layout_constraintTop_toTopOf="parent"> - + + + + - + diff --git a/app/src/main/res/layout/item_attendance_calculator_header.xml b/app/src/main/res/layout/item_attendance_calculator_header.xml new file mode 100644 index 00000000..debc7997 --- /dev/null +++ b/app/src/main/res/layout/item_attendance_calculator_header.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attendance_summary.xml b/app/src/main/res/layout/item_attendance_summary.xml index b4946195..340ea892 100644 --- a/app/src/main/res/layout/item_attendance_summary.xml +++ b/app/src/main/res/layout/item_attendance_summary.xml @@ -3,7 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical"> + android:orientation="vertical" + tools:context=".ui.modules.attendance.summary.AttendanceSummaryAdapter"> @@ -37,7 +37,6 @@ @@ -46,7 +45,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_present" android:textSize="14sp" /> @@ -57,18 +55,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="50" /> + + @@ -77,7 +77,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_unexcused" android:textSize="14sp" /> @@ -88,18 +87,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> + + @@ -108,7 +109,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_excused" android:textSize="14sp" /> @@ -119,18 +119,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="25" /> + + @@ -139,7 +141,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_absence_school" android:textSize="14sp" /> @@ -150,18 +151,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> + + @@ -170,7 +173,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_exemption" android:textSize="14sp" /> @@ -181,18 +183,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="6" /> + + @@ -201,7 +205,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_unexcused_lateness" android:textSize="14sp" /> @@ -212,18 +215,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> + + @@ -232,7 +237,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="20dp" - android:layout_marginLeft="20dp" android:layout_weight="1" android:text="@string/attendance_excused_lateness" android:textSize="14sp" /> @@ -243,11 +247,14 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginEnd="25dp" - android:layout_marginRight="25dp" android:gravity="end" android:textSize="12sp" tools:text="0" /> + + diff --git a/app/src/main/res/layout/item_attendance_summary_subject.xml b/app/src/main/res/layout/item_attendance_summary_subject.xml index 263c08a7..ff731461 100644 --- a/app/src/main/res/layout/item_attendance_summary_subject.xml +++ b/app/src/main/res/layout/item_attendance_summary_subject.xml @@ -3,7 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/attendanceSummaryItemSubject" android:layout_width="match_parent" - android:layout_height="48dp" + android:layout_height="wrap_content" + android:minHeight="48dp" android:gravity="start" android:maxLines="1" android:paddingLeft="16dp" diff --git a/app/src/main/res/layout/item_completed_lesson.xml b/app/src/main/res/layout/item_completed_lesson.xml index 8b49ba76..e9944f56 100644 --- a/app/src/main/res/layout/item_completed_lesson.xml +++ b/app/src/main/res/layout/item_completed_lesson.xml @@ -3,21 +3,20 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/ic_all_divider" - android:foreground="?selectableItemBackground" + android:background="?selectableItemBackground" android:paddingStart="8dp" - android:paddingLeft="8dp" android:paddingTop="6dp" android:paddingEnd="12dp" - android:paddingRight="12dp" android:paddingBottom="6dp" - tools:context=".ui.modules.timetable.completed.CompletedLessonItem" - tools:ignore="UnusedAttribute"> + tools:context=".ui.modules.timetable.completed.CompletedLessonsAdapter"> diff --git a/app/src/main/res/layout/item_conference.xml b/app/src/main/res/layout/item_conference.xml new file mode 100644 index 00000000..4c881f34 --- /dev/null +++ b/app/src/main/res/layout/item_conference.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_contributor.xml b/app/src/main/res/layout/item_contributor.xml index e84faf59..9b126343 100644 --- a/app/src/main/res/layout/item_contributor.xml +++ b/app/src/main/res/layout/item_contributor.xml @@ -3,32 +3,32 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="56dp" android:background="?selectableItemBackground" - android:orientation="vertical"> + android:minHeight="56dp" + android:orientation="vertical" + tools:context=".ui.modules.about.contributor.ContributorAdapter"> + android:layout_marginBottom="8dp" + android:contentDescription="@string/contributor_avatar_description" + tools:src="@tools:sample/avatars" /> + tools:text="@tools:sample/lorem" /> diff --git a/app/src/main/res/layout/item_dashboard_account.xml b/app/src/main/res/layout/item_dashboard_account.xml new file mode 100644 index 00000000..7ae0c84d --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_account.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_admin_message.xml b/app/src/main/res/layout/item_dashboard_admin_message.xml new file mode 100644 index 00000000..407e1292 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_admin_message.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_ads.xml b/app/src/main/res/layout/item_dashboard_ads.xml new file mode 100644 index 00000000..b75bb27e --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_ads.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_announcements.xml b/app/src/main/res/layout/item_dashboard_announcements.xml new file mode 100644 index 00000000..b9ddb757 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_announcements.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_conferences.xml b/app/src/main/res/layout/item_dashboard_conferences.xml new file mode 100644 index 00000000..b02b8e18 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_conferences.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_exams.xml b/app/src/main/res/layout/item_dashboard_exams.xml new file mode 100644 index 00000000..84302403 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_exams.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_grades.xml b/app/src/main/res/layout/item_dashboard_grades.xml new file mode 100644 index 00000000..345d8a5e --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_grades.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_homework.xml b/app/src/main/res/layout/item_dashboard_homework.xml new file mode 100644 index 00000000..b36afc57 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_homework.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml new file mode 100644 index 00000000..1c9246a1 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_lessons.xml b/app/src/main/res/layout/item_dashboard_lessons.xml new file mode 100644 index 00000000..a40f17f2 --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_lessons.xml @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_debug.xml b/app/src/main/res/layout/item_debug.xml new file mode 100644 index 00000000..f0f9f09f --- /dev/null +++ b/app/src/main/res/layout/item_debug.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/app/src/main/res/layout/item_debug_notifications.xml b/app/src/main/res/layout/item_debug_notifications.xml new file mode 100644 index 00000000..204f7000 --- /dev/null +++ b/app/src/main/res/layout/item_debug_notifications.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_exam.xml b/app/src/main/res/layout/item_exam.xml index 459a7ecb..bd1d4731 100644 --- a/app/src/main/res/layout/item_exam.xml +++ b/app/src/main/res/layout/item_exam.xml @@ -4,14 +4,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - tools:context=".ui.modules.exam.ExamItem"> + tools:context=".ui.modules.exam.ExamAdapter"> @@ -22,7 +21,6 @@ android:layout_height="wrap_content" android:layout_below="@id/examItemSubject" android:layout_alignStart="@id/examItemSubject" - android:layout_alignLeft="@id/examItemSubject" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:textSize="13sp" @@ -34,15 +32,11 @@ android:layout_height="wrap_content" android:layout_below="@id/examItemSubject" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_marginStart="10dp" - android:layout_marginLeft="10dp" android:layout_marginTop="5dp" android:layout_marginEnd="20dp" - android:layout_marginRight="20dp" android:layout_marginBottom="10dp" android:layout_toEndOf="@id/examItemType" - android:layout_toRightOf="@id/examItemType" android:gravity="end" android:textSize="13sp" tools:text="@tools:sample/lorem" /> diff --git a/app/src/main/res/layout/item_grade_details.xml b/app/src/main/res/layout/item_grade_details.xml index d6d99d9b..6849e929 100644 --- a/app/src/main/res/layout/item_grade_details.xml +++ b/app/src/main/res/layout/item_grade_details.xml @@ -1,82 +1,103 @@ - + android:background="?android:windowBackground"> - - - + android:background="?selectableItemBackground" + android:paddingStart="12dp" + android:paddingTop="7dp" + android:paddingEnd="12dp" + android:paddingBottom="7dp" + tools:context=".ui.modules.grade.details.GradeDetailsAdapter"> - + - + - - + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_grade_statistics_header.xml b/app/src/main/res/layout/item_grade_statistics_header.xml new file mode 100644 index 00000000..cc35f606 --- /dev/null +++ b/app/src/main/res/layout/item_grade_statistics_header.xml @@ -0,0 +1,51 @@ + + + + + + + +