1
0
Fork 1

Compare commits

..

No commits in common. "1.9.0" and "1.5.0" have entirely different histories.
1.9.0 ... 1.5.0

383 changed files with 5740 additions and 28809 deletions

View file

@ -1,12 +0,0 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=4
[*.json]
indent_size=2
[*.{kt,kts}]
disabled_rules=import-ordering,no-wildcard-imports

View file

@ -1,4 +1,4 @@
name: Deploy release name: Deploy to app stores
on: on:
release: release:
@ -7,17 +7,16 @@ on:
jobs: jobs:
deploy-google-play: deploy-google-play:
name: Google Play name: Deploy to google play
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
environment: google-play environment: google-play
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v1
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v3 - uses: actions/cache@v2
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -38,22 +37,20 @@ jobs:
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }} ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }} SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }} SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace; run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery: deploy-app-gallery:
name: AppGallery name: Deploy to AppGallery
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
environment: app-gallery environment: app-gallery
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v1
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v3 - uses: actions/cache@v2
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches

View file

@ -1,4 +1,4 @@
name: Deploy DEV name: Deploy to app tests
on: on:
push: push:
@ -18,12 +18,11 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
environment: app-center environment: app-center
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v1
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v3 - uses: actions/cache@v2
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -67,7 +66,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }} BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assembleFdroidDebug --stacktrace run: ./gradlew assembleFdroidDebug --stacktrace
- name: Upload apk to github artifacts - name: Upload apk to github artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
@ -88,12 +87,11 @@ jobs:
environment: app-distribution environment: app-distribution
if: github.event_name != 'pull_request_target' if: github.event_name != 'pull_request_target'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v1
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v3 - uses: actions/cache@v2
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -133,7 +131,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }} BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
- name: Upload apk to github artifacts - name: Upload apk to github artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
path: app/build/outputs/apk/play/debug/app-play-debug.apk path: app/build/outputs/apk/play/debug/app-play-debug.apk

View file

@ -2,28 +2,24 @@ name: Tests
on: on:
push: push:
branches: branches: [ master, develop ]
- master
- develop
- 'hotfix/**'
tags: [ '*' ] tags: [ '*' ]
pull_request: pull_request:
branches: [ master, develop ]
jobs: jobs:
unit-tests:
tests-fdroid: name: Unit tests
name: F-Droid
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: fkirc/skip-duplicate-actions@master - uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1 - uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2 - uses: actions/setup-java@v1
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v3 - uses: actions/cache@v2
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -33,58 +29,6 @@ jobs:
run: | run: |
./gradlew testFdroidDebugUnitTest --stacktrace ./gradlew testFdroidDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace ./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3 - uses: codecov/codecov-action@v1
with:
flags: unit
tests-play:
name: Play
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testPlayDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit
tests-hms:
name: HMS
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testHmsDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with: with:
flags: unit flags: unit

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2023 Wulkanowy Copyright 2021 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,13 +1,18 @@
Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) [English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/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) [![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/) [![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) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
@ -34,7 +39,7 @@ Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
* podpora více účtů s možností přejmenování žáků * podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv * tmavý a černý (AMOLED) motiv
* offline režim * offline režim
* volitelné reklamy na podporu projektu * žádné reklamy
## Stáhnout ## Stáhnout
@ -52,7 +57,7 @@ Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGal
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
## Postaveno s pomocí ## Postaveno s
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) * [Wulkanowy SDK](https://github.com/wulkanowy/sdk)

View file

@ -1,13 +1,14 @@
[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) [Polska wersja README](README.md)
[English version of README](README.en.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/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) [![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/) [![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) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
@ -21,7 +22,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Prozentsatz der Anwesenheit * Prozentsatz der Anwesenheit
* Prüfungen * Prüfungen
* Stundenplan * Stundenplan
* abgeschlossene Unterrichtsstunden * Unterricht abgeschlossen
* Nachrichten * Nachrichten
* Hausaufgaben * Hausaufgaben
* Anmerkungen * Anmerkungen
@ -34,7 +35,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern * Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema * dunkles und schwarzes (AMOLED) Thema
* Offline-Modus * Offline-Modus
* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen * keine Werbung
## Herunterladen ## Herunterladen
@ -50,7 +51,7 @@ Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGal
alt="Explore it on AppGallery" alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=) height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Sie können auch eine [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) die beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden Sie können auch ein [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) das beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
## Gebaut mit ## Gebaut mit

View file

@ -1,13 +1,18 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) [Polska wersja README](README.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/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) [![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/) [![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) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Unofficial android VULCAN UONET+ register client for both students and their parents Unofficial android VULCAN UONET+ register client for both students and their parents
@ -34,7 +39,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
* support for multiple accounts with the ability to rename students * support for multiple accounts with the ability to rename students
* dark and black (AMOLED) theme * dark and black (AMOLED) theme
* offline mode * offline mode
* optional ads which allow to support the project * no ads
## Download ## Download

View file

@ -1,13 +1,18 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md) [English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/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) [![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/) [![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) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
@ -34,7 +39,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia * obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw * ciemny i czarny (AMOLED) motyw
* tryb offline * tryb offline
* opcjonalne reklamy umożliwiające wsparcie projektu * brak reklam
## Pobierz ## Pobierz

View file

@ -1,13 +1,18 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia [English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Česká verze README](README.cs.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/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) [![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/) [![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) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
@ -34,7 +39,7 @@ Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
* podpora viacerých účtov s možnosťou premenovania žiakov * podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív * tmavý a čierny (AMOLED) motív
* offline režim * offline režim
* voliteľné reklamy na podporu projektu * žiadne reklamy
## Stiahnuť ## Stiahnuť
@ -52,7 +57,7 @@ Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGa
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
## Postavené s pomocou ## Postavené s
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) * [Wulkanowy SDK](https://github.com/wulkanowy/sdk)

View file

@ -15,35 +15,33 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle' apply from: 'hooks.gradle'
android { android {
namespace 'io.github.wulkanowy' compileSdkVersion 31
compileSdkVersion 33
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 31
versionCode 119 versionCode 103
versionName "1.9.0" versionName "1.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
manifestPlaceholders = [ manifestPlaceholders = [
firebase_enabled: project.hasProperty("enableFirebase"), firebase_enabled: project.hasProperty("enableFirebase"),
admob_project_id: "" admob_project_id: ""
] ]
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments += [ arguments += [
"room.schemaLocation": "$projectDir/schemas".toString(), "room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true" "room.incremental" : "true"
] ]
} }
} }
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
if (System.env.SET_BUILD_TIMESTAMP) { if (System.env.SET_BUILD_TIMESTAMP) {
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis()) buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
@ -75,8 +73,6 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
} }
debug { debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV" resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"
@ -96,12 +92,10 @@ android {
play { play {
dimension "platform" dimension "platform"
manifestPlaceholders = [ manifestPlaceholders = [
install_channel : "Google Play", install_channel : "Google Play",
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713" 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", "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 { fdroid {
@ -126,8 +120,6 @@ android {
testOptions.unitTests { testOptions.unitTests {
includeAndroidResources = true includeAndroidResources = true
// workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750
all { jvmArgs '-noverify' }
} }
compileOptions { compileOptions {
@ -138,14 +130,12 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
} }
packagingOptions { packagingOptions {
resources { exclude 'META-INF/library_release.kotlin_module'
excludes += ['META-INF/library_release.kotlin_module', exclude 'META-INF/library-core_release.kotlin_module'
'META-INF/library-core_release.kotlin_module']
}
} }
aboutLibraries { aboutLibraries {
@ -161,7 +151,7 @@ play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.10d userFraction = 0.25d
updatePriority = 1 updatePriority = 1
enabled.set(false) enabled.set(false)
} }
@ -179,42 +169,42 @@ huaweiPublish {
ext { ext {
work_manager = "2.7.1" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.4.3" room = "2.4.0"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.13.3" mockk = "1.12.1"
coroutines = "1.6.4" coroutines = "1.6.0"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.9.0" implementation "io.github.wulkanowy:sdk:1.5.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.9.0" implementation "androidx.core:core-ktx:1.7.0"
implementation 'androidx.core:core-splashscreen:1.0.0' implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation "androidx.activity:activity-ktx:1.6.1" implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.5.1" implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.annotation:annotation:1.5.0" implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.2"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.7.0" implementation "com.google.android.material:material:1.4.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.3.0' implementation 'com.github.lopspower:CircularImageView:4.2.0'
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -230,51 +220,49 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.10.0" implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:2.2.2" implementation "io.coil-kt:coil:1.4.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.8.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0'
implementation 'org.apache.commons:commons-text:1.10.0'
playImplementation platform('com.google.firebase:firebase-bom:31.1.1') playImplementation platform('com.google.firebase:firebase-bom:29.0.3')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core:1.10.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:21.4.0' playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301' hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' 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 "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.9.1' testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.5.1" testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.4" testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.test:core:1.4.0"
testImplementation "androidx.room:room-testing:$room" testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation "androidx.test:runner:1.5.1" androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.4" androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,92 +1,33 @@
{ {
"agcgw": { "agcgw":{
"backurl": "connect-dre.hispace.hicloud.com", "backurl":"connect-dre.dbankcloud.cn",
"url": "connect-dre.dbankcloud.cn", "url":"connect-dre.hispace.hicloud.com"
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com", },
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn" "client":{
}, "cp_id":"890048000024105546",
"agcgw_all": { "product_id":"",
"CN": "connect-drcn.dbankcloud.cn", "client_id":"",
"CN_back": "connect-drcn.hispace.hicloud.com", "client_secret":"",
"DE": "connect-dre.dbankcloud.cn", "app_id":"101440411",
"DE_back": "connect-dre.hispace.hicloud.com", "package_name":"io.github.wulkanowy.dev",
"RU": "connect-drru.hispace.dbankcloud.ru", "api_key":""
"RU_back": "connect-drru.hispace.dbankcloud.cn", },
"SG": "connect-dra.dbankcloud.cn", "service":{
"SG_back": "connect-dra.hispace.hicloud.com" "analytics":{
}, "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"websocketgw_all": { "resource_id":"p1",
"CN": "connect-ws-drcn.hispace.dbankcloud.cn", "channel_id":""
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com", },
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
},
"client": {
"cp_id": "890048000024105546",
"product_id": "736430079244736562",
"client_id": "514530959291319360",
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
"project_id": "736430079244736562",
"app_id": "106552551",
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
"package_name": "io.github.wulkanowy.dev"
},
"oauth_client": {
"client_id": "106552551",
"client_type": 1
},
"app_info": {
"app_id": "106552551",
"package_name": "io.github.wulkanowy.dev"
},
"service": {
"analytics": {
"collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"resource_id": "p1",
"channel_id": ""
},
"search":{ "search":{
"url":"https://search-dre.cloud.huawei.com" "url":"https://search-dre.cloud.huawei.com"
}, },
"cloudstorage": { "cloudstorage":{
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia", "storage_url":"https://ops-dre.agcstorage.link"
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru", },
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru", "ml":{
"storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu", "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
"storage_url_de": "https://ops-dre.agcstorage.link", }
"storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn", },
"storage_url_sg": "https://ops-dra.agcstorage.link", "region":"DE",
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn", "configuration_version":"1.0"
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
},
"ml": {
"mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
}
},
"region": "DE",
"configuration_version": "3.0",
"appInfos": [
{
"package_name": "io.github.wulkanowy.dev",
"client": {
"app_id": "106552551"
},
"app_info": {
"package_name": "io.github.wulkanowy.dev",
"app_id": "106552551"
},
"oauth_client": {
"client_type": 1,
"client_id": "106552551"
}
}
]
} }

View file

@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" /> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" /> <foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
<monochrome android:drawable="@drawable/ic_launcher_foreground_dev_mono" />
</adaptive-icon> </adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,28 +0,0 @@
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 javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = 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)

View file

@ -8,7 +8,15 @@ import javax.inject.Singleton
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
class AnalyticsHelper @Inject constructor() { class AnalyticsHelper @Inject constructor() {
fun logEvent(name: String, vararg params: Pair<String, Any?>) = Unit fun logEvent(name: String, vararg params: Pair<String, Any?>) {
fun setCurrentScreen(activity: Activity, name: String?) = Unit // do nothing
fun popCurrentScreen(name: String?) = Unit }
fun setCurrentScreen(activity: Activity, name: String?) {
// do nothing
}
fun popCurrentScreen(name: String?) {
// do nothing
}
} }

View file

@ -1,28 +0,0 @@
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 javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = 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)

View file

@ -3,38 +3,26 @@ package io.github.wulkanowy.utils
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import com.huawei.agconnect.crash.AGConnectCrash
import com.huawei.hms.analytics.HiAnalytics import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class AnalyticsHelper @Inject constructor( class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) { ) {
private val analytics by lazy { HiAnalytics.getInstance(context) } 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<String, Any?>) { fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply { Bundle().apply {
params.forEach { (key, value) -> params.forEach {
if (value == null) return@forEach if (it.second == null) return@forEach
when (value) { when (it.second) {
is String -> putString(key, value) is String, is String? -> putString(it.first, it.second as String)
is Int -> putInt(key, value) is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean -> putBoolean(key, value) is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
} }
} }
analytics.onEvent(name, this) analytics.onEvent(name, this)

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.utils
import android.util.Log import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree import fr.bipi.tressence.base.FormatterPriorityTree
import fr.bipi.tressence.common.StackTraceRecorder
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
@ -23,10 +22,16 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return if (skipLog(priority, tag, message, t)) return
// Disabled due to a bug in the Huawei library
/*connectCrash.setCustomKey("priority", priority)
connectCrash.setCustomKey("tag", tag.orEmpty())
connectCrash.setCustomKey("message", message)
if (t != null) { if (t != null) {
connectCrash.recordException(t) connectCrash.recordException(t)
} else { } else {
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
} }*/
} }
} }

View file

@ -7,7 +7,6 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Suppress("UNUSED_PARAMETER", "unused")
class InAppReviewHelper @Inject constructor( class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) { ) {

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -8,8 +9,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries> <queries>
<intent> <intent>
@ -37,14 +37,13 @@
<application <application
android:name=".WulkanowyApp" android:name=".WulkanowyApp"
android:allowBackup="false" android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
tools:ignore="DataExtractionRules,UnusedAttribute"> tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"
android:exported="true" android:exported="true"

View file

@ -31,14 +31,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var analyticsHelper: AnalyticsHelper lateinit var analyticsHelper: AnalyticsHelper
@Inject
lateinit var adsHelper: AdsHelper
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initializeAppLanguage() initializeAppLanguage()
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
adsHelper.initialize()
initLogging() initLogging()
} }

View file

@ -19,6 +19,7 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -109,6 +110,7 @@ internal class DataModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
@Provides @Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) = fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
@ -195,7 +197,7 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao
@Singleton @Singleton
@Provides @Provides

View file

@ -1,173 +1,23 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import kotlinx.coroutines.flow.* data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
import kotlinx.coroutines.sync.Mutex companion object {
import kotlinx.coroutines.sync.withLock fun <T> success(data: T?): Resource<T> {
import timber.log.Timber return Resource(Status.SUCCESS, data, null)
sealed class Resource<T> {
open class Loading<T> : Resource<T>()
data class Intermediate<T>(val data: T) : Loading<T>()
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val error: Throwable) : Resource<T>()
}
val <T> Resource<T>.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> null
is Resource.Error -> null
}
val <T> Resource<T>.errorOrNull: Throwable?
get() = when (this) {
is Resource.Error -> this.error
else -> null
}
fun <T> resourceFlow(block: suspend () -> T) = flow {
emit(Resource.Loading())
emit(Resource.Success(block()))
}.catch { emit(Resource.Error(it)) }
fun <T> flatResourceFlow(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.Loading())
emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading })
}.catch { emit(Resource.Error(it)) }
fun <T, U> Resource<T>.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)
}
fun <T> Flow<Resource<T>>.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")
}
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
it.mapData(block)
}
fun <T> Flow<Resource<T>>.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 <T> Flow<Resource<T>>.onResourceLoading(block: suspend () -> Unit) = onEach {
if (it is Resource.Loading) {
block()
}
}
fun <T> Flow<Resource<T>>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Intermediate) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Success) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
if (it is Resource.Error) {
block(it.error)
}
}
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
if (it !is Resource.Loading) {
block()
}
}
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loading }.first()
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
inline fun <ResultType, RequestType> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (ResultType) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline filterResult: (ResultType) -> ResultType = { it }
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val filteredResult = filterResult(data)
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
emit(Resource.Intermediate(filteredResult))
} }
try { fun <T> error(error: Throwable?, data: T? = null): Resource<T> {
val newData = fetch(data) return Resource(Status.ERROR, data, error)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(filterResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
} }
} else {
query().map { Resource.Success(filterResult(it)) } fun <T> loading(data: T? = null): Resource<T> {
}) return Resource(Status.LOADING, data, null)
}
}
} }
@JvmName("networkBoundResourceWithMap") enum class Status {
inline fun <ResultType, RequestType, T> networkBoundResource( LOADING,
mutex: Mutex = Mutex(), SUCCESS,
showSavedOnLoading: Boolean = true, ERROR
crossinline isResultEmpty: (T) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline mapResult: (ResultType) -> T
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val mappedResult = mapResult(data)
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
emit(Resource.Intermediate(mappedResult))
}
try {
val newData = fetch(data)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
}
} else {
query().map { Resource.Success(mapResult(it)) }
})
} }

View file

@ -1,10 +1,72 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.content.Context import android.content.Context
import androidx.room.* 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.RoomDatabase.JournalMode.TRUNCATE
import io.github.wulkanowy.data.db.dao.* import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.entities.* 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.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
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.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
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.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
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.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
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.* import io.github.wulkanowy.data.db.migrations.*
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton import javax.inject.Singleton
@ -30,7 +92,7 @@ import javax.inject.Singleton
Subject::class, Subject::class,
LuckyNumber::class, LuckyNumber::class,
CompletedLesson::class, CompletedLesson::class,
Mailbox::class, ReportingUnit::class,
Recipient::class, Recipient::class,
MobileDevice::class, MobileDevice::class,
Teacher::class, Teacher::class,
@ -46,8 +108,6 @@ import javax.inject.Singleton
autoMigrations = [ autoMigrations = [
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47), AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
AutoMigration(from = 51, to = 52),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -56,7 +116,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 54 const val VERSION_SCHEMA = 47
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -103,11 +163,6 @@ abstract class AppDatabase : RoomDatabase() {
Migration43(), Migration43(),
Migration44(), Migration44(),
Migration46(), Migration46(),
Migration49(),
Migration50(),
Migration51(),
Migration53(),
Migration54(),
) )
fun newInstance( fun newInstance(
@ -158,7 +213,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val completedLessonsDao: CompletedLessonsDao abstract val completedLessonsDao: CompletedLessonsDao
abstract val mailboxDao: MailboxDao abstract val reportingUnitDao: ReportingUnitDao
abstract val recipientDao: RecipientDao abstract val recipientDao: RecipientDao

View file

@ -1,14 +1,11 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.*
import java.util.*
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.Month import java.time.Month
@ -61,11 +58,4 @@ class Converters {
emptyList() // handle errors from old gson Pair serialized data 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)
} }

View file

@ -2,12 +2,11 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Update import androidx.room.Update
interface BaseDao<T> { interface BaseDao<T> {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert
suspend fun insertAll(items: List<T>): List<Long> suspend fun insertAll(items: List<T>): List<Long>
@Update @Update

View file

@ -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.Mailbox
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE email = :email")
suspend fun loadAll(email: String): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId")
fun loadAll(email: String, symbol: String, schoolId: String): Flow<List<Mailbox>>
}

View file

@ -11,12 +11,9 @@ import kotlinx.coroutines.flow.Flow
interface MessagesDao : BaseDao<Message> { interface MessagesDao : BaseDao<Message> {
@Transaction @Transaction
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?> fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>> fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
fun loadAll(folder: Int, email: String): Flow<List<Message>>
} }

View file

@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface MobileDeviceDao : BaseDao<MobileDevice> { interface MobileDeviceDao : BaseDao<MobileDevice> {
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC") @Query("SELECT * FROM MobileDevices WHERE student_id = :userLoginId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>> fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
} }

View file

@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import javax.inject.Singleton import javax.inject.Singleton
@ -10,6 +9,6 @@ import javax.inject.Singleton
@Dao @Dao
interface RecipientDao : BaseDao<Recipient> { interface RecipientDao : BaseDao<Recipient> {
@Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey") @Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role")
suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List<Recipient> suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient>
} }

View file

@ -0,0 +1,17 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.ReportingUnit
import javax.inject.Singleton
@Singleton
@Dao
interface ReportingUnitDao : BaseDao<ReportingUnit> {
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId")
suspend fun load(studentId: Int): List<ReportingUnit>
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId AND real_id = :unitId")
suspend fun loadOne(studentId: Int, unitId: Int): ReportingUnit?
}

View file

@ -10,6 +10,6 @@ import javax.inject.Singleton
@Singleton @Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> { interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC") @Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>> fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
} }

View file

@ -33,10 +33,6 @@ abstract class StudentDao {
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters> abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
@Transaction
@Query("SELECT * FROM Students WHERE id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters?
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)

View file

@ -13,7 +13,4 @@ interface TimetableDao : BaseDao<Timetable> {
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @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): Flow<List<Timetable>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
} }

View file

@ -24,8 +24,5 @@ data class GradeSemesterStatistics(
var id: Long = 0 var id: Long = 0
@Transient @Transient
var classAverage: String = "" var average: String = ""
@Transient
var studentAverage: String = ""
} }

View file

@ -1,32 +0,0 @@
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,
}

View file

@ -9,19 +9,23 @@ import java.time.Instant
@Entity(tableName = "Messages") @Entity(tableName = "Messages")
data class Message( data class Message(
@ColumnInfo(name = "email") @ColumnInfo(name = "student_id")
val email: String, val studentId: Long,
@ColumnInfo(name = "message_global_key") @ColumnInfo(name = "real_id")
val messageGlobalKey: String, val realId: Int,
@ColumnInfo(name = "mailbox_key")
val mailboxKey: String,
@ColumnInfo(name = "message_id") @ColumnInfo(name = "message_id")
val messageId: Int, val messageId: Int,
val correspondents: String, @ColumnInfo(name = "sender_name")
val sender: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "recipient_name")
val recipient: String,
val subject: String, val subject: String,
@ -32,11 +36,7 @@ data class Message(
var unread: Boolean, var unread: Boolean,
@ColumnInfo(name = "read_by") val removed: Boolean,
val readBy: Int?,
@ColumnInfo(name = "unread_by")
val unreadBy: Int?,
@ColumnInfo(name = "has_attachments") @ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean val hasAttachments: Boolean
@ -48,7 +48,11 @@ data class Message(
@ColumnInfo(name = "is_notified") @ColumnInfo(name = "is_notified")
var isNotified: Boolean = true var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = "" var content: String = ""
var sender: String? = null
var recipients: String? = null
} }

View file

@ -12,8 +12,11 @@ data class MessageAttachment(
@ColumnInfo(name = "real_id") @ColumnInfo(name = "real_id")
val realId: Int, val realId: Int,
@ColumnInfo(name = "message_global_key") @ColumnInfo(name = "message_id")
val messageGlobalKey: String, val messageId: Int,
@ColumnInfo(name = "one_drive_id")
val oneDriveId: String,
@ColumnInfo(name = "url") @ColumnInfo(name = "url")
val url: String, val url: String,

View file

@ -7,6 +7,6 @@ data class MessageWithAttachment(
@Embedded @Embedded
val message: Message, val message: Message,
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key") @Relation(parentColumn = "message_id", entityColumn = "message_id")
val attachments: List<MessageAttachment> val attachments: List<MessageAttachment>
) )

View file

@ -9,7 +9,7 @@ import java.time.Instant
@Entity(tableName = "MobileDevices") @Entity(tableName = "MobileDevices")
data class MobileDevice( data class MobileDevice(
@ColumnInfo(name = "user_login_id") @ColumnInfo(name = "student_id")
val userLoginId: Int, val userLoginId: Int,
@ColumnInfo(name = "device_id") @ColumnInfo(name = "device_id")

View file

@ -4,7 +4,6 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
import java.time.Instant import java.time.Instant
@Entity(tableName = "Notifications") @Entity(tableName = "Notifications")
@ -19,9 +18,6 @@ data class Notification(
val type: NotificationType, val type: NotificationType,
@ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}")
val destination: Destination,
val date: Instant, val date: Instant,
val data: String? = null val data: String? = null

View file

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.db.entities package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
@ -7,16 +8,32 @@ import java.io.Serializable
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
@Entity(tableName = "Recipients") @Entity(tableName = "Recipients")
data class Recipient( data class Recipient(
val mailboxGlobalKey: String,
val studentMailboxGlobalKey: String, @ColumnInfo(name = "student_id")
val fullName: String, val studentId: Int,
val userName: String,
val schoolShortName: String, @ColumnInfo(name = "real_id")
val type: MailboxType, 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
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
override fun toString() = userName override fun toString() = name
} }

View file

@ -0,0 +1,32 @@
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 unitId: Int,
@ColumnInfo(name = "short")
val shortName: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "sender_name")
val senderName: String,
val roles: List<Int>
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View file

@ -9,8 +9,8 @@ import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements") @Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement( data class SchoolAnnouncement(
@ColumnInfo(name = "user_login_id") @ColumnInfo(name = "student_id")
val userLoginId: Int, val studentId: Int,
val date: LocalDate, val date: LocalDate,

View file

@ -1,23 +0,0 @@
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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements")
database.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()
)
}
}

View file

@ -1,21 +0,0 @@
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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MobileDevices")
database.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()
)
}
}

View file

@ -1,88 +0,0 @@
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(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
recreateMessageAttachmentsTable(database)
recreateRecipientsTable(database)
deleteReportingUnitTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MessageAttachments")
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Recipients")
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS ReportingUnits")
}
}

View file

@ -1,57 +0,0 @@
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(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.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()
)
}
}

View file

@ -1,26 +0,0 @@
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(database: SupportSQLiteDatabase) {
migrateResman(database)
removeTomaszowMazowieckiStudents(database)
}
private fun migrateResman(database: SupportSQLiteDatabase) {
database.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(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'")
}
}

View file

@ -2,8 +2,7 @@ package io.github.wulkanowy.data.enums
enum class GradeSortingMode(val value: String) { enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"), ALPHABETIC("alphabetic"),
DATE("date"), DATE("date");
AVERAGE("average");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC

View file

@ -3,22 +3,17 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Semester 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.Attendance as SdkAttendance
import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary
fun List<SdkAttendance>.mapToEntities(semester: Semester, lessons: List<Timetable>) = map { fun List<SdkAttendance>.mapToEntities(semester: Semester) = map {
Attendance( Attendance(
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
date = it.date, date = it.date,
timeId = it.timeId, timeId = it.timeId,
number = it.number, number = it.number,
subject = it.subject.ifBlank { subject = it.subject,
lessons.find { lesson ->
lesson.date == it.date && lesson.number == it.number
}?.subject.orEmpty()
},
name = it.name, name = it.name,
presence = it.presence, presence = it.presence,
absence = it.absence, absence = it.absence,

View file

@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map { fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement( SchoolAnnouncement(
userLoginId = student.userLoginId, studentId = student.userLoginId,
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
content = it.content, content = it.content,

View file

@ -1,20 +0,0 @@
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<SdkMailbox>.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,
)
}

View file

@ -1,46 +1,40 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.sdk.pojo.MailboxType import io.github.wulkanowy.data.db.entities.MessageAttachment
import timber.log.Timber import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkMessage>.mapToEntities( fun List<SdkMessage>.mapToEntities(student: Student) = map {
student: Student,
mailbox: Mailbox?,
allMailboxes: List<Mailbox>
): List<Message> = map {
Message( Message(
messageGlobalKey = it.globalKey, studentId = student.id,
mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> realId = it.id ?: 0,
box.fullName == it.mailbox messageId = it.messageId ?: 0,
}?.globalKey.let { mailboxKey -> sender = it.sender?.name.orEmpty(),
if (mailboxKey == null) { senderId = it.sender?.loginId ?: 0,
Timber.e("Can't find ${it.mailbox} in $allMailboxes") recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
"unknown"
} else mailboxKey
},
email = student.email,
messageId = it.id,
correspondents = it.correspondents,
subject = it.subject.trim(), subject = it.subject.trim(),
date = it.dateZoned.toInstant(), date = it.dateZoned?.toInstant() ?: Instant.now(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread, unread = it.unread ?: false,
unreadBy = it.unreadBy, removed = it.removed,
readBy = it.readBy, hasAttachments = it.hasAttachments
hasAttachments = it.hasAttachments,
).apply { ).apply {
content = it.content.orEmpty() content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
} }
} }
fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map { fun List<SdkMessageAttachment>.mapToEntities() = map {
MessageAttachment( MessageAttachment(
messageGlobalKey = messageGlobalKey, realId = it.id,
realId = it.url.hashCode(), messageId = it.messageId,
oneDriveId = it.oneDriveId,
url = it.url, url = it.url,
filename = it.filename filename = it.filename
) )
@ -48,11 +42,12 @@ fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
fun List<Recipient>.mapFromEntities() = map { fun List<Recipient>.mapFromEntities() = map {
SdkRecipient( SdkRecipient(
fullName = it.fullName, id = it.realId,
userName = it.userName, name = it.realName,
studentName = it.userName, loginId = it.loginId,
mailboxGlobalKey = it.mailboxGlobalKey, reportingUnitId = it.unitId,
schoolNameShort = it.schoolShortName, role = it.role,
type = MailboxType.valueOf(it.type.name), hash = it.hash,
shortName = it.name
) )
} }

View file

@ -1,14 +1,14 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
import io.github.wulkanowy.sdk.pojo.Token as SdkToken import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(student: Student) = map { fun List<SdkDevice>.mapToEntities(semester: Semester) = map {
MobileDevice( MobileDevice(
userLoginId = student.userLoginId, userLoginId = semester.studentId,
date = it.createDateZoned.toInstant(), date = it.createDateZoned.toInstant(),
deviceId = it.id, deviceId = it.id,
name = it.name name = it.name

View file

@ -1,16 +1,17 @@
package io.github.wulkanowy.data.mappers 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.data.db.entities.Recipient
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkRecipient>.mapToEntities(studentMailboxGlobalKey: String) = map { fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map {
Recipient( Recipient(
mailboxGlobalKey = it.mailboxGlobalKey, studentId = userLoginId,
fullName = it.fullName, realId = it.id,
userName = it.userName, realName = it.name,
studentMailboxGlobalKey = studentMailboxGlobalKey, name = it.shortName,
schoolShortName = it.schoolNameShort, hash = it.hash,
type = MailboxType.valueOf(it.type.name), loginId = it.loginId,
role = it.role,
unitId = it.reportingUnitId ?: 0
) )
} }

View file

@ -1,87 +0,0 @@
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 io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.mapper.mapSemesters
import java.time.Instant
import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent
import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser
fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser(
email = email,
login = login,
password = password,
baseUrl = baseUrl,
loginType = loginType,
symbols = symbols.map { registerSymbol ->
RegisterSymbol(
symbol = registerSymbol.symbol,
error = registerSymbol.error,
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<SdkRegisterStudent>()
.map { registerSubject ->
RegisterStudent(
studentId = registerSubject.studentId,
studentName = registerSubject.studentName,
studentSecondName = registerSubject.studentSecondName,
studentSurname = registerSubject.studentSurname,
className = registerSubject.className,
classId = registerSubject.classId,
isParent = registerSubject.isParent,
semesters = registerSubject.semesters
.mapSemesters()
.mapToEntities(registerSubject.studentId),
)
},
)
}
)
}
)
fun RegisterStudent.mapToStudentWithSemesters(
user: RegisterUser,
symbol: RegisterSymbol,
unit: RegisterUnit,
colors: List<Long>,
): 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,
schoolName = unit.schoolName,
schoolShortName = unit.schoolShortName,
schoolSymbol = unit.schoolId,
studentName = "$studentName $studentSurname",
loginMode = Sdk.Mode.SCRAPPER.name,
scrapperBaseUrl = user.baseUrl,
mobileBaseUrl = "",
certificateKey = "",
privateKey = "",
password = user.password,
isCurrent = false,
registrationDate = Instant.now(),
).apply {
avatarColor = colors.random()
},
)

View file

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
ReportingUnit(
studentId = student.id.toInt(),
unitId = it.id,
roles = it.roles,
senderId = it.senderId,
senderName = it.senderName,
shortName = it.short
)
}

View file

@ -1,10 +1,10 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import android.content.Intent
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
data class NotificationData( data class NotificationData(
val destination: Destination, val intentToStart: Intent,
val title: String, val title: String,
val content: String val content: String
) )
@ -13,7 +13,7 @@ data class GroupNotificationData(
val notificationDataList: List<NotificationData>, val notificationDataList: List<NotificationData>,
val title: String, val title: String,
val content: String, val content: String,
val destination: Destination, val intentToStart: Intent,
val type: NotificationType val type: NotificationType
) )

View file

@ -1,43 +0,0 @@
package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.Semester
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 baseUrl: String,
val loginType: Scrapper.LoginType,
val symbols: List<RegisterSymbol>,
) : java.io.Serializable
data class RegisterSymbol(
val symbol: String,
val error: Throwable?,
val userName: String,
val schools: List<RegisterUnit>,
) : java.io.Serializable
data class RegisterUnit(
val userLoginId: Int,
val schoolId: String,
val schoolName: String,
val schoolShortName: String,
val parentIds: List<Int>,
val studentIds: List<Int>,
val employeeIds: List<Int>,
val error: Throwable?,
val students: List<RegisterStudent>,
) : 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<Semester>,
) : java.io.Serializable

View file

@ -3,8 +3,8 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.api.AdminMessageService import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -19,7 +19,6 @@ class AdminMessageRepository @Inject constructor(
suspend fun getAdminMessages(student: Student) = networkBoundResource( suspend fun getAdminMessages(student: Student) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
query = { adminMessageDao.loadAll() }, query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() }, fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true }, shouldFetch = { true },

View file

@ -1,19 +1,15 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.AttendanceDao 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.Attendance
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
@ -23,7 +19,6 @@ import javax.inject.Singleton
@Singleton @Singleton
class AttendanceRepository @Inject constructor( class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao, private val attendanceDb: AttendanceDao,
private val timetableDb: TimetableDao,
private val sdk: Sdk, private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -41,7 +36,6 @@ class AttendanceRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)
@ -52,15 +46,10 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
}, },
fetch = { fetch = {
val lessons = withContext(Dispatchers.IO) {
timetableDb.load(
semester.diaryId, semester.studentId, start.monday, end.sunday
)
}
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId) .getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester, lessons) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new) attendanceDb.deleteAll(old uniqueSubtract new)

View file

@ -4,12 +4,8 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -32,7 +28,6 @@ class AttendanceSummaryRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired

View file

@ -4,7 +4,6 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -31,7 +30,6 @@ class CompletedLessonsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)

View file

@ -5,12 +5,8 @@ import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.Instant import java.time.Instant
@ -36,7 +32,6 @@ class ConferenceRepository @Inject constructor(
startDate: Instant = Instant.EPOCH, startDate: Instant = Instant.EPOCH,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired

View file

@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -34,7 +33,6 @@ class ExamRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)

View file

@ -7,7 +7,6 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -37,10 +36,6 @@ class GradeRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, 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) -> shouldFetch = { (details, summaries) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired

View file

@ -11,12 +11,8 @@ import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems
import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -46,7 +42,6 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = partialMutex, mutex = partialMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(partialCacheKey, semester) key = getRefreshKey(partialCacheKey, semester)
@ -68,16 +63,20 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> { "Wszystkie" -> {
val summaryItem = GradePartialStatistics( val numerator = items.map {
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId, studentId = semester.studentId,
semesterId = semester.semesterId, semesterId = semester.semesterId,
subject = subjectName, subject = subjectName,
classAverage = items.map { it.classAverage }.getSummaryAverage(), classAverage = if (numerator.isEmpty()) "" else numerator.average().let {
studentAverage = items.map { it.studentAverage }.getSummaryAverage(), "%.2f".format(Locale.FRANCE, it)
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
) )).reversed()
listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
@ -91,7 +90,6 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = semesterMutex, mutex = semesterMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(semesterCacheKey, semester) key = getRefreshKey(semesterCacheKey, semester)
@ -114,29 +112,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item -> val itemsWithAverage = items.map { item ->
item.copy().apply { item.copy().apply {
val denominator = item.amounts.sum() val denominator = item.amounts.sum()
classAverage = if (denominator == 0) "" else { average = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount -> (item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount (gradeValue + 1) * amount
}.sum().toDouble() / denominator).asAverageString() }.sum().toDouble() / denominator).let {
"%.2f".format(Locale.FRANCE, it)
}
} }
} }
} }
when (subjectName) { when (subjectName) {
"Wszystkie" -> { "Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
val summaryItem = GradeSemesterStatistics( studentId = semester.studentId,
studentId = semester.studentId, semesterId = semester.semesterId,
semesterId = semester.semesterId, subject = subjectName,
subject = subjectName, amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), studentGrade = 0
studentGrade = 0, ).apply {
).apply { average = itemsWithAverage.mapNotNull {
classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage() it.average.replace(",", ".").toDoubleOrNull()
studentAverage = items }.average().let {
.mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } } "%.2f".format(Locale.FRANCE, it)
.average().asAverageString()
} }
listOf(summaryItem) + itemsWithAverage }).reversed()
}
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -149,7 +147,6 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = pointsMutex, mutex = pointsMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
@ -174,19 +171,6 @@ class GradeStatisticsRepository @Inject constructor(
} }
) )
private fun List<String>.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<List<Int>>.sumGradeAmounts(): List<Int> { private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0) val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach { forEach {

View file

@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -33,7 +32,6 @@ class HomeworkRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)

View file

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -29,7 +29,6 @@ class LuckyNumberRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) }, query = { luckyNumberDb.load(student.studentId, now()) },
fetch = { fetch = {

View file

@ -3,32 +3,35 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider 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.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.* 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.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.SentMessage
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -41,93 +44,98 @@ class MessageRepository @Inject constructor(
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider, private val sharedPrefProvider: SharedPrefProvider,
private val json: Json, private val json: Json,
private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val messagesCacheKey = "message" private val cacheKey = "message"
private val mailboxCacheKey = "mailboxes"
@Suppress("UNUSED_PARAMETER")
fun getMessages( fun getMessages(
student: Student, student: Student,
mailbox: Mailbox?, semester: Semester,
folder: MessageFolder, folder: MessageFolder,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
): Flow<Resource<List<Message>>> = networkBoundResource( ): Flow<Resource<List<Message>>> = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(messagesCacheKey, mailbox, folder) key = getRefreshKey(cacheKey, student, folder)
) )
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
if (mailbox == null) {
messagesDb.loadAll(folder.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
},
fetch = { fetch = {
sdk.init(student).getMessages( sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now())
folder = Folder.valueOf(folder.name), .mapToEntities(student)
mailboxKey = mailbox?.globalKey,
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach { messagesDb.insertAll((new uniqueSubtract old).onEach {
it.isNotified = !notify it.isNotified = !notify
}) })
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
refreshHelper.updateLastRefreshTimestamp( refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
getRefreshKey(messagesCacheKey, mailbox, folder)
)
} }
) )
private fun getMessagesWithReadByChange(
old: List<Message>,
new: List<Message>,
setNotified: Boolean
): List<Message> {
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) }
val updatedItems = newMeta uniqueSubtract oldMeta
return updatedItems.map {
val oldItem = old.find { item -> item.messageId == it.first.messageId }
it.first.apply {
id = oldItem?.id ?: 0
isNotified = oldItem?.isNotified ?: setNotified
content = oldItem?.content.orEmpty()
}
}
}
fun getMessage( fun getMessage(
student: Student, student: Student,
message: Message, message: Message,
markAsRead: Boolean = false, markAsRead: Boolean = false,
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource( ): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
isResultEmpty = { it?.message?.content.isNullOrBlank() },
shouldFetch = { shouldFetch = {
checkNotNull(it) { "This message no longer exist!" } checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isBlank()}") Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isBlank() it.message.unread || it.message.content.isEmpty()
},
query = {
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
}, },
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) },
fetch = { fetch = {
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead) sdk.init(student).getMessageDetails(
messageId = it!!.message.messageId,
folderId = message.folderId,
read = markAsRead,
id = message.realId
).let { details ->
details.content to details.attachments.mapToEntities()
}
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old) { "Fetched message no longer exist!" } checkNotNull(old, { "Fetched message no longer exist!" })
messagesDb.updateAll( messagesDb.updateAll(listOf(old.message.apply {
listOf(old.message.apply { id = old.message.id
id = message.id unread = !markAsRead
unread = !markAsRead content = content.ifBlank { downloadedMessage }
sender = new.sender }))
recipients = new.recipients.singleOrNull() ?: "Wielu adresatów" messageAttachmentDao.insertAttachments(attachments)
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") Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
} }
) )
fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow<List<Message>> { fun getMessagesFromDatabase(student: Student): Flow<List<Message>> {
return if (mailbox == null) { return messagesDb.loadAll(student.id.toInt(), RECEIVED.id)
messagesDb.loadAll(RECEIVED.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
} }
suspend fun updateMessages(messages: List<Message>) { suspend fun updateMessages(messages: List<Message>) {
@ -139,88 +147,31 @@ class MessageRepository @Inject constructor(
subject: String, subject: String,
content: String, content: String,
recipients: List<Recipient>, recipients: List<Recipient>,
mailboxId: String, ): SentMessage = sdk.init(student).sendMessage(
) { subject = subject,
sdk.init(student).sendMessage( content = content,
subject = subject, recipients = recipients.mapFromEntities()
content = content,
recipients = recipients.mapFromEntities(),
mailboxId = mailboxId,
)
}
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
val firstMessage = messages.first()
sdk.init(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)
getMessages(
student = student,
mailbox = mailbox,
folder = TRASHED,
forceRefresh = true,
).first()
}
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
deleteMessages(student, mailbox, listOf(message))
}
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 = {
sdk.init(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? { suspend fun deleteMessage(student: Student, message: Message) {
val mailbox = getMailboxByStudentUseCase(student) val isDeleted = sdk.init(student).deleteMessages(
messages = listOf(message.messageId), message.folderId
)
return if (mailbox == null) { if (message.folderId != MessageFolder.TRASHED.id && isDeleted) {
getMailboxes(student, forceRefresh = true) val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply {
.onResourceError { throw it } id = message.id
.onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") } content = message.content
.waitForResult() }
messagesDb.updateAll(listOf(deletedMessage))
getMailboxByStudentUseCase(student) } else messagesDb.deleteAll(listOf(message))
} else mailbox
} }
var draftMessage: MessageDraft? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { json.decodeFromString(it) } ?.let { json.decodeFromString(it) }
set(value) = sharedPrefProvider.putString( set(value) = sharedPrefProvider.putString(
context.getString(R.string.pref_key_message_draft), context.getString(R.string.pref_key_message_send_draft),
value?.let { json.encodeToString(it) } value?.let { json.encodeToString(it) }
) )
} }

View file

@ -6,13 +6,9 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -34,17 +30,16 @@ class MobileDeviceRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { mobileDb.loadAll(student.userLoginId) }, query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
fetch = { fetch = {
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getRegisteredDevices() .getRegisteredDevices()
.mapToEntities(student) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mobileDb.deleteAll(old uniqueSubtract new) mobileDb.deleteAll(old uniqueSubtract new)

View file

@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -31,7 +30,6 @@ class NoteRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
getRefreshKey(cacheKey, semester) getRefreshKey(cacheKey, semester)

View file

@ -10,16 +10,17 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.Instant import java.time.Instant
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
class PreferencesRepository @Inject constructor( class PreferencesRepository @Inject constructor(
@ApplicationContext val context: Context, @ApplicationContext val context: Context,
@ -221,31 +222,19 @@ class PreferencesRepository @Inject constructor(
get() = selectedDashboardTilesPreference.asFlow() get() = selectedDashboardTilesPreference.asFlow()
.map { set -> .map { set ->
set.map { DashboardItem.Tile.valueOf(it) } set.map { DashboardItem.Tile.valueOf(it) }
.plus( .plus(DashboardItem.Tile.ACCOUNT)
listOfNotNull( .plus(DashboardItem.Tile.ADMIN_MESSAGE)
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.toSet() .toSet()
} }
var selectedDashboardTiles: Set<DashboardItem.Tile> var selectedDashboardTiles: Set<DashboardItem.Tile>
get() = selectedDashboardTilesPreference.get() get() = selectedDashboardTilesPreference.get()
.map { DashboardItem.Tile.valueOf(it) } .map { DashboardItem.Tile.valueOf(it) }
.plus( .plus(DashboardItem.Tile.ACCOUNT)
listOfNotNull( .plus(DashboardItem.Tile.ADMIN_MESSAGE)
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.toSet() .toSet()
set(value) { set(value) {
val filteredValue = value.filterNot { val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
it == DashboardItem.Tile.ACCOUNT || it == DashboardItem.Tile.ADMIN_MESSAGE
}
.map { it.name } .map { it.name }
.toSet() .toSet()
@ -282,48 +271,7 @@ class PreferencesRepository @Inject constructor(
var isAppReviewDone: Boolean var isAppReviewDone: Boolean
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value) } set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply()
var isAppSupportShown: Boolean
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
var isAgreeToProcessData: Boolean
get() = getBoolean(
R.string.pref_key_ads_consent_data_processing,
R.bool.pref_default_ads_consent_data_processing
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
}
var isPersonalizedAdsEnabled: Boolean
get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, 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 installationId: String
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
init {
if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString()
}
}
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
@ -340,14 +288,19 @@ class PreferencesRepository @Inject constructor(
private fun getBoolean(id: String, default: Int) = private fun getBoolean(id: String, default: Int) =
sharedPref.getBoolean(id, context.resources.getBoolean(default)) sharedPref.getBoolean(id, context.resources.getBoolean(default))
private fun getBoolean(id: Int, default: Boolean) =
sharedPref.getBoolean(context.getString(id), default)
private companion object { private companion object {
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_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_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_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" 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_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
} }
} }

View file

@ -1,7 +1,10 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.* 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.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -20,10 +23,9 @@ class RecipientRepository @Inject constructor(
private val cacheKey = "recipient" private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) { suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
val new = sdk.init(student).getRecipients(mailbox.globalKey) val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
.mapToEntities(mailbox.globalKey) val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
val old = recipientDb.loadAll(type, mailbox.globalKey)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.deleteAll(old uniqueSubtract new)
recipientDb.insertAll(new uniqueSubtract old) recipientDb.insertAll(new uniqueSubtract old)
@ -31,33 +33,18 @@ class RecipientRepository @Inject constructor(
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
suspend fun getRecipients( suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
student: Student, val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
mailbox: Mailbox?,
type: MailboxType,
): List<Recipient> {
mailbox ?: return emptyList()
val cached = recipientDb.loadAll(type, mailbox.globalKey)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
return if (cached.isEmpty() || isExpired) { return if (cached.isEmpty() || isExpired) {
refreshRecipients(student, mailbox, type) refreshRecipients(student, unit, role)
recipientDb.loadAll(type, mailbox.globalKey) recipientDb.loadAll(unit.studentId, unit.unitId, role)
} else cached } else cached
} }
suspend fun getMessageSender( suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
student: Student, return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
mailbox: Mailbox?, .mapToEntities(student.studentId)
message: Message,
): List<Recipient> {
mailbox ?: return emptyList()
return sdk.init(student)
.getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
.mapToEntities(mailbox.globalKey)
} }
} }

View file

@ -0,0 +1,42 @@
package io.github.wulkanowy.data.repositories
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.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ReportingUnitRepository @Inject constructor(
private val reportingUnitDb: ReportingUnitDao,
private val sdk: Sdk
) {
suspend fun refreshReportingUnits(student: Student) {
val new = sdk.init(student).getReportingUnits().mapToEntities(student)
val old = reportingUnitDb.load(student.id.toInt())
reportingUnitDb.deleteAll(old.uniqueSubtract(new))
reportingUnitDb.insertAll(new.uniqueSubtract(old))
}
suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
return reportingUnitDb.load(student.id.toInt()).ifEmpty {
refreshReportingUnits(student)
reportingUnitDb.load(student.id.toInt())
}
}
suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? {
return reportingUnitDb.loadOne(student.id.toInt(), unitId) ?: run {
refreshReportingUnits(student)
return reportingUnitDb.loadOne(student.id.toInt(), unitId)
}
}
}

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -28,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
fun getSchoolAnnouncements( fun getSchoolAnnouncements(
student: Student, student: Student,
forceRefresh: Boolean, forceRefresh: Boolean, notify: Boolean = false
notify: Boolean = false
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { query = {
schoolAnnouncementDb.loadAll(student.userLoginId) schoolAnnouncementDb.loadAll(student.studentId)
}, },
fetch = { fetch = {
sdk.init(student) sdk.init(student)
@ -57,7 +55,7 @@ class SchoolAnnouncementRepository @Inject constructor(
) )
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> { fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
return schoolAnnouncementDb.loadAll(student.userLoginId) return schoolAnnouncementDb.loadAll(student.studentId)
} }
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -30,7 +30,6 @@ class SchoolRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student) key = getRefreshKey(cacheKey, student)

View file

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,7 +25,6 @@ class StudentInfoRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) }, query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = { fetch = {

View file

@ -11,8 +11,6 @@ import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToEntities 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.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
@ -54,14 +52,6 @@ class StudentRepository @Inject constructor(
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol) sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToEntities(password, appInfo.defaultColorsForAvatar) .mapToEntities(password, appInfo.defaultColorsForAvatar)
suspend fun getUserSubjectsFromScrapper(
email: String,
password: String,
scrapperBaseUrl: String,
symbol: String
): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToPojo(password)
suspend fun getStudentsHybrid( suspend fun getStudentsHybrid(
email: String, email: String,
password: String, password: String,
@ -83,15 +73,6 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) =
studentDb.loadStudentWithSemestersById(id)?.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) {
decrypt(student.password)
}
}
}
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()

View file

@ -4,12 +4,8 @@ import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -31,7 +27,6 @@ class SubjectRepository @Inject constructor(
forceRefresh: Boolean = false, forceRefresh: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired

View file

@ -4,12 +4,8 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -31,7 +27,6 @@ class TeacherRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired

View file

@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
@ -31,10 +30,6 @@ class TimetableRepository @Inject constructor(
private val cacheKey = "timetable" private val cacheKey = "timetable"
enum class TimetableType {
NORMAL, ADDITIONAL
}
fun getTimetable( fun getTimetable(
student: Student, student: Student,
semester: Semester, semester: Semester,
@ -42,16 +37,9 @@ class TimetableRepository @Inject constructor(
end: LocalDate, end: LocalDate,
forceRefresh: Boolean, forceRefresh: Boolean,
refreshAdditional: Boolean = false, refreshAdditional: Boolean = false,
notify: Boolean = false, notify: Boolean = false
timetableType: TimetableType = TimetableType.NORMAL
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = {
when (timetableType) {
TimetableType.NORMAL -> it.lessons.isEmpty()
TimetableType.ADDITIONAL -> it.additional.isEmpty()
}
},
shouldFetch = { (timetable, additional, headers) -> shouldFetch = { (timetable, additional, headers) ->
val refreshKey = getRefreshKey(cacheKey, semester, start, end) val refreshKey = getRefreshKey(cacheKey, semester, start, end)
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)

View file

@ -1,32 +0,0 @@
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<LocalDate?> {
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()
}
}

View file

@ -1,65 +0,0 @@
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<Mailbox>.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.first() + "*".repeat(it.length - 1)
}
}
}

View file

@ -10,18 +10,19 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -58,7 +59,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
Timber.d("Receiving intent... ${intent.toUri(0)}") Timber.d("Receiving intent... ${intent.toUri(0)}")
resourceFlow { flowWithResource {
val showStudentName = !studentRepository.isOneUniqueStudent() val showStudentName = !studentRepository.isOneUniqueStudent()
val student = studentRepository.getCurrentStudent(false) val student = studentRepository.getCurrentStudent(false)
val studentId = intent.getIntExtra(STUDENT_ID, 0) val studentId = intent.getIntExtra(STUDENT_ID, 0)
@ -68,9 +69,9 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
} else { } else {
Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
} }
} }.onEach {
.onResourceError { Timber.e(it) } if (it.status == Status.ERROR) Timber.e(it.error!!)
.launchIn(GlobalScope) }.launchIn(GlobalScope)
} }
private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) {

View file

@ -15,41 +15,76 @@ import javax.inject.Singleton
@Singleton @Singleton
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
fun initializeShortcuts() { private val destinations = mapOf(
"grade" to Destination.Grade,
"attendance" to Destination.Attendance,
"exam" to Destination.Exam,
"timetable" to Destination.Timetable()
)
init {
initializeShortcuts()
}
fun getDestination(intent: Intent) =
destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)]
private fun initializeShortcuts() {
val shortcutsInfo = listOf( val shortcutsInfo = listOf(
ShortcutInfoCompat.Builder(context, "grade_shortcut") ShortcutInfoCompat.Builder(context, "grade_shortcut")
.setShortLabel(context.getString(R.string.grade_title)) .setShortLabel(context.getString(R.string.grade_title))
.setLongLabel(context.getString(R.string.grade_title)) .setLongLabel(context.getString(R.string.grade_title))
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
.setIntent(SplashActivity.getStartIntent(context, Destination.Grade) .setIntent(SplashActivity.getStartIntent(context)
.apply { action = Intent.ACTION_VIEW }) .apply {
action = Intent.ACTION_VIEW
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade")
}
)
.build(), .build(),
ShortcutInfoCompat.Builder(context, "attendance_shortcut") ShortcutInfoCompat.Builder(context, "attendance_shortcut")
.setShortLabel(context.getString(R.string.attendance_title)) .setShortLabel(context.getString(R.string.attendance_title))
.setLongLabel(context.getString(R.string.attendance_title)) .setLongLabel(context.getString(R.string.attendance_title))
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
.setIntent(SplashActivity.getStartIntent(context, Destination.Attendance) .setIntent(SplashActivity.getStartIntent(context)
.apply { action = Intent.ACTION_VIEW }) .apply {
action = Intent.ACTION_VIEW
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance")
}
)
.build(), .build(),
ShortcutInfoCompat.Builder(context, "exam_shortcut") ShortcutInfoCompat.Builder(context, "exam_shortcut")
.setShortLabel(context.getString(R.string.exam_title)) .setShortLabel(context.getString(R.string.exam_title))
.setLongLabel(context.getString(R.string.exam_title)) .setLongLabel(context.getString(R.string.exam_title))
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
.setIntent(SplashActivity.getStartIntent(context, Destination.Exam) .setIntent(SplashActivity.getStartIntent(context)
.apply { action = Intent.ACTION_VIEW }) .apply {
action = Intent.ACTION_VIEW
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam")
}
)
.build(), .build(),
ShortcutInfoCompat.Builder(context, "timetable_shortcut") ShortcutInfoCompat.Builder(context, "timetable_shortcut")
.setShortLabel(context.getString(R.string.timetable_title)) .setShortLabel(context.getString(R.string.timetable_title))
.setLongLabel(context.getString(R.string.timetable_title)) .setLongLabel(context.getString(R.string.timetable_title))
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
.setIntent(SplashActivity.getStartIntent(context, Destination.Timetable()) .setIntent(SplashActivity.getStartIntent(context)
.apply { action = Intent.ACTION_VIEW }) .apply {
action = Intent.ACTION_VIEW
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable")
}
)
.build() .build()
) )
shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) } shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
} }
private companion object {
private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id"
}
} }

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