1
0
Fork 1

Compare commits

..

No commits in common. "1.4.4" and "0.22.0" have entirely different histories.

856 changed files with 9732 additions and 68565 deletions

View file

@ -1,12 +1,3 @@
---
name: Bug report
about: Utwórz raport błędu, aby pomóc nam ulepszyć Wulkanowego
title: ''
labels: ''
assignees: ''
---
## Co powinno się dziać

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Zaproponuj nowy pomysł dla Wulkanowego
title: ''
labels: ''
assignees: ''
---
** Czy Twoja prośba o funkcję jest związana z problemem? Proszę opisz.**
Jasny i zwięzły opis problemu. Np. Zawsze jestem sfrustrowany, gdy [...]
** Opisz żądane rozwiązanie **
Jasny i zwięzły opis tego, co chcesz, aby się wydarzyło.
** Opisz alternatywy, które rozważałeś **
Jasny i zwięzły opis wszelkich rozważanych alternatywnych rozwiązań lub funkcji.
** Dodatkowy kontekst **
Dodaj inny kontekst lub zrzuty ekranu dotyczące żądania funkcji tutaj.

View file

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
target-branch: develop
ignore:
- dependency-name: io.github.wulkanowy:sdk
reviewers:
- Faierbel

View file

@ -1,76 +0,0 @@
name: Deploy to app stores
on:
release:
types: [ created ]
jobs:
deploy-google-play:
name: Deploy to google play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Decrypt keys
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Upload apk to google play
env:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery:
name: Deploy to AppGallery
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-gallery
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Decrypt keys
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Prepare credentials
env:
AGC_CREDENTIALS: ${{ secrets.AGC_CREDENTIALS }}
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
- name: Build and publish HMS version
env:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace

View file

@ -1,144 +0,0 @@
name: Deploy to app tests
on:
push:
# branches: [ develop ]
branches: [ '!*' ]
pull_request_target:
# branches: [ develop ]
branches: [ '!*' ]
workflow_dispatch:
jobs:
deploy-appcenter:
name: App Center
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-center
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Set run number with offset
env:
BUILD_NUMBER_OFFSET: ${{ secrets.BUILD_NUMBER_OFFSET }}
run: echo "RUN_NUMBER=$((GITHUB_RUN_NUMBER+BUILD_NUMBER_OFFSET))" >> $GITHUB_ENV
- name: Prepare build configuration
run: |
sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle
sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/google-services.json
sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/agconnect-services.json
sed -i -e '/versionNameSuffix/d' app/build.gradle
- name: Add signing config
run: |
cat >> app/build.gradle <<EOF
android.signingConfigs.debug {
storeFile file("bitrise.jks")
storePassword System.getenv("BITRISE_KEYSTORE_PASSWORD")
keyAlias System.getenv("BITRISE_KEY_ALIAS")
keyPassword System.getenv("BITRISE_KEY_PASSWORD")
}
EOF
- name: Decrypt keys
env:
BITRISE_ENCRYPT_KEY: ${{ secrets.BITRISE_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$BITRISE_ENCRYPT_KEY ./app/bitrise.jks.gpg
- name: Bump version
uses: chkfung/android-version-actions@v1.1
with:
gradlePath: app/build.gradle
versionCode: ${{ env.RUN_NUMBER }}
versionName: ${{ env.RUN_NUMBER }}-${{ github.head_ref }}
- name: Build apk
env:
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assembleFdroidDebug --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v2
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
- name: Deploy to app center
uses: wzieba/AppCenter-Github-Action@v1
with:
appName: wulkanowy/wulkanowy
token: ${{ secrets.APP_CENTER_TOKEN }}
group: Testers
file: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
notifyTesters: true
debug: true
deploy-app-distribution:
name: App Distribution
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-distribution
if: github.event_name != 'pull_request_target'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Set run number with offset
env:
BUILD_NUMBER_OFFSET: ${{ secrets.BUILD_NUMBER_OFFSET }}
run: echo "RUN_NUMBER=$((GITHUB_RUN_NUMBER+BUILD_NUMBER_OFFSET))" >> $GITHUB_ENV
- name: Add signing config
run: |
cat >> app/build.gradle <<EOF
android.signingConfigs.debug {
storeFile file("bitrise.jks")
storePassword System.getenv("BITRISE_KEYSTORE_PASSWORD")
keyAlias System.getenv("BITRISE_KEY_ALIAS")
keyPassword System.getenv("BITRISE_KEY_PASSWORD")
}
EOF
- name: Decrypt keys
env:
BITRISE_ENCRYPT_KEY: ${{ secrets.BITRISE_ENCRYPT_KEY }}
BITRISE_SERVICES_ENCRYPT_KEY: ${{ secrets.BITRISE_SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$BITRISE_SERVICES_ENCRYPT_KEY ./app/src/debug/google-services.json.gpg
gpg --yes --batch --passphrase=$BITRISE_ENCRYPT_KEY ./app/bitrise.jks.gpg
- name: Bump version
uses: chkfung/android-version-actions@v1.1
with:
gradlePath: app/build.gradle
versionCode: ${{ env.RUN_NUMBER }}
versionName: ${{ env.RUN_NUMBER }}
- name: Build apk
env:
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v2
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
path: app/build/outputs/apk/play/debug/app-play-debug.apk
- name: Deploy to app distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
token: ${{ secrets.FIREBASE_TOKEN }}
groups: discord
file: app/build/outputs/apk/play/debug/app-play-debug.apk

View file

@ -1,34 +0,0 @@
name: Tests
on:
push:
branches: [ master, develop ]
tags: [ '*' ]
pull_request:
branches: [ master, develop ]
jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testFdroidDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v1
with:
flags: unit

6
.gitignore vendored
View file

@ -19,7 +19,6 @@ out/
# Gradle files
.gradle/
build/
.build-cache
# Local configuration file (sdk path, etc)
local.properties
@ -114,8 +113,3 @@ Thumbs.db
!/gradle/wrapper/gradle-wrapper.jar
.idea/jarRepositories.xml
app/src/release/agconnect-services.json
app/src/release/agconnect-credentials.json
.idea/deploymentTargetDropDown.xml

View file

@ -7,11 +7,29 @@
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="false" />
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="false" />
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="false" />
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="false" />
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" />
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" />
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
@ -125,11 +143,13 @@
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>

View file

@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- 0.24.0
- 0.22.0
android:
licenses:
@ -58,7 +58,6 @@ script:
- |
if [ $TRAVIS_TAG ]; then
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
./gradlew publishPlayRelease -PenableFirebase --stacktrace;

View file

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

View file

@ -1,78 +0,0 @@
[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
[![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)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
## Funkce
* přihlášení pomocí emailu a hesla
* funkce z webové stránky deníku:
* známky
* statistiky známek
* frekvence
* procento frekvence
* zkoušky
* plán lekce
* dokončené lekce
* zprávy
* domácí úkoly
* poznámky
* šťastné číslo
* další lekce
* školní setkání
* informace o žáku a škole
* výpočet průměru nezávisle na preferencích školy
* upozornění, např. o nových známkách
* podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv
* offline režim
* žádné reklamy
## Stáhnout
Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGallery
[<img src="https://play.google.com/intl/cs-CZ/badges/images/generic/cs_badge_web_generic.png"
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stáhnout s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/baTGiDP.png"
alt="Objevuj v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
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
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráce
Přispějte do projektu vytvořením PR nebo odesláním issue na GitHub.
Pro zájemce o překlad aplikace do různých jazyků poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licence
Tento projekt je licencován pod licencí Apache License 2.0 - podrobnosti v souboru [LICENSE](LICENSE)

View file

@ -1,74 +0,0 @@
[Polska wersja README](README.md)
[English version of README](README.en.md)
# Wulkanowy
[![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)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
## Merkmale
* Einloggen mit E-Mail und Passwort
* Funktionen von der Registerwebsite:
* Noten
* Notenstatistik
* Anwesenheit
* Prozentsatz der Anwesenheit
* Prüfungen
* Stundenplan
* Unterricht abgeschlossen
* Nachrichten
* Hausaufgaben
* Anmerkungen
* Glückszahl
* Zusätzliche Lektionen
* Schulkonferenzen
* Schüler- und Schulinformationen
* Berechnung des Durchschnitts unabhängig von den Präferenzen der Schule
* Benachrichtigungen, z. B. über eine neue Note
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema
* Offline-Modus
* keine Werbung
## Herunterladen
Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGallery store herunterladen
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
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=)
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
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Beitragen
Bitte tragen Sie zum Projekt bei, indem Sie entweder eine PR erstellen oder ein Issue auf GitHub einreichen.
Für Personen, die daran interessiert sind, die Anwendung in verschiedene Sprachen zu übersetzen, bieten wir Crowdin
https://crowdin.com/project/wulkanowy2
## Lizenz
Dieses Projekt ist unter der Apache License 2.0 lizenziert - siehe die [LIZENZ](LICENSE) Datei für Details

View file

@ -1,14 +1,7 @@
[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
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
@ -18,7 +11,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
## Features
* logging in using the email and password
* logging in using the email and password OR using token and pin
* functions from the register website:
* grades
* grade statistics
@ -31,19 +24,15 @@ Unofficial android VULCAN UONET+ register client for both students and their par
* homework
* notes
* lucky number
* additional lessons
* school conferences
* student and school information
* calculation of the average independently of school's preferences
* notifications, e.g. about a new grade
* support for multiple accounts with the ability to rename students
* dark and black (AMOLED) theme
* offline mode
* no ads
## Download
You can download the current version from the Google Play, F-Droid or Huawei AppGallery store
You can download the current beta version from the Google Play, F-Droid or Huawei AppGallery store
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
@ -61,8 +50,7 @@ You can also download a [development version](https://wulkanowy.github.io/#downl
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Dagger 2](https://github.com/google/dagger)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
@ -70,9 +58,6 @@ You can also download a [development version](https://wulkanowy.github.io/#downl
Please contribute to the project either by creating a PR or submitting an issue on GitHub.
For people interested in translating the application into different languages, we provide Crowdin
https://crowdin.com/project/wulkanowy2
## License
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details

View file

@ -1,14 +1,7 @@
[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
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
@ -18,7 +11,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
## Funkcje
* logowanie za pomocą e-maila i hasła
* logowanie za pomocą e-maila i hasła LUB tokena i pinu
* funkcje ze strony internetowej dziennika:
* oceny
* statystyki ocen
@ -31,19 +24,15 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
* zadania domowe
* uwagi
* szczęśliwy numerek
* dodatkowe lekcje
* zebrania w szkole
* informacje o uczniu i szkole
* obliczanie średniej niezależnie od preferencji szkoły
* powiadomienia np. o nowej ocenie
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw
* tryb offline
* tryb offilne
* brak reklam
## Pobierz
Aktualną wersję możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
Aktualną wersję beta możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Pobierz z Google Play"
@ -61,9 +50,8 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa
## Zbudowana za pomocą
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Wulkanowy SDK](https://github.com/wulkanowy/SDK)
* [Dagger 2](https://github.com/google/dagger)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
@ -71,9 +59,6 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa
Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub.
Dla osób zainteresowanych tłumaczeniem aplikacji na różne języki udostępniamy Crowdina
https://crowdin.com/project/wulkanowy2
## Licencja
Ten projekt udostępniany jest na licencji Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE)

View file

@ -1,78 +0,0 @@
[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
[![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)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
## Funkcie
* prihlásenie pomocou emailu a hesla
* funkcie z webovej stránky denníka:
* známky
* štatistiky známok
* frekvencia
* percento frekvencie
* skúšky
* plán lekcie
* dokončené lekcie
* správy
* domáce úlohy
* poznámky
* šťastné číslo
* ďalšie lekcie
* školské stretnutie
* informácie o žiakovi a škole
* výpočet priemeru nezávisle od preferencií školy
* upozornenia, napr. o nových známkach
* podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív
* offline režim
* žiadne reklamy
## Stiahnuť
Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGallery
[<img src="https://play.google.com/intl/sk/badges/images/generic/sk_badge_web_generic.png"
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stiahnuť s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/sX8UyAw.png"
alt="Objavíte v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
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
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráca
Prispejte do projektu vytvorením PR alebo odoslaním issue na GitHub.
Pre záujemcov o preklad aplikácie do rôznych jazykov poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licencia
Tento projekt je licencovaný pod licenciou Apache License 2.0 - podrobnosti v súbore [LICENSE](LICENSE)

Binary file not shown.

View file

@ -1,36 +1,31 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.github.triplet.play'
apply plugin: 'ru.cian.huawei-publish'
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'com.huawei.agconnect'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
compileSdkVersion 31
compileSdkVersion 30
buildToolsVersion '30.0.2'
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 31
versionCode 102
versionName "1.4.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
minSdkVersion 17
targetSdkVersion 30
versionCode 73
versionName "0.22.0"
multiDexEnabled true
resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [
firebase_enabled: project.hasProperty("enableFirebase"),
admob_project_id: ""
firebase_enabled: project.hasProperty("enableFirebase")
]
javaCompileOptions {
annotationProcessorOptions {
@ -40,19 +35,10 @@ android {
]
}
}
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
if (System.env.SET_BUILD_TIMESTAMP) {
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
} else {
buildConfigField "long", "BUILD_TIMESTAMP", "1486235849000"
}
}
sourceSets {
// https://github.com/robolectric/robolectric/issues/3928#issuecomment-395309991
debug.assets.srcDirs += files("$projectDir/schemas".toString())
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
signingConfigs {
@ -70,66 +56,44 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
}
debug {
resValue "string", "app_name", "Wulkanowy DEV"
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
testCoverageEnabled = project.hasProperty('coverage')
ext.enableCrashlytics = project.hasProperty("enableFirebase")
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
}
}
flavorDimensions "platform"
productFlavors {
hms {
dimension "platform"
manifestPlaceholders = [install_channel: "AppGallery"]
}
play {
dimension "platform"
manifestPlaceholders = [
install_channel : "Google Play",
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
]
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
}
fdroid {
dimension "platform"
manifestPlaceholders = [install_channel: "F-Droid"]
}
}
playConfigs {
play { enabled.set(true) }
}
buildFeatures {
viewBinding true
viewBinding = true
}
bundle {
language {
enableSplit = false
}
}
testOptions.unitTests {
includeAndroidResources = true
lintOptions {
disable 'HardwareIds'
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "1.8"
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
}
@ -143,66 +107,57 @@ android {
}
}
kapt {
correctErrorTypes true
}
play {
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false
track = 'beta'
updatePriority = 4
enabled.set(false)
}
huaweiPublish {
instances {
hmsRelease {
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
buildFormat = "aab"
deployType = "draft"
}
}
track = 'alpha'
updatePriority = 0
}
ext {
work_manager = "2.7.1"
android_hilt = "1.0.0"
room = "2.3.0"
chucker = "3.5.2"
mockk = "1.12.1"
coroutines = "1.5.2"
work_manager = "2.4.0"
room = "2.2.5"
chucker = "3.3.0"
mockk = "1.10.2"
moshi = "1.11.0"
}
configurations.all {
resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3"
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.4.4"
implementation "io.github.wulkanowy:sdk:0.22.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "androidx.core:core-ktx:1.7.0"
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat-resources:1.2.0"
implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.google.android.material:material:1.2.1"
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.2.0'
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
@ -210,57 +165,48 @@ dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
kapt "androidx.hilt:hilt-compiler:$android_hilt"
implementation "androidx.hilt:hilt-work:$android_hilt"
implementation 'androidx.hilt:hilt-work:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
implementation 'com.github.ncapdevi:FragNav:3.3.0'
implementation "com.github.YarikSOffice:lingver:1.3.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.2.2"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:1.4.0"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:1.0.0-rc3"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
playImplementation platform('com.google.firebase:firebase-bom:29.0.2')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.android.play:core:1.10.2'
playImplementation 'com.google.firebase:firebase-analytics:17.6.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1"
playImplementation 'com.google.firebase:firebase-messaging:20.3.0'
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.300'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.13.2"
testImplementation "junit:junit:4.13"
testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9'
testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0"
testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:core:1.3.0"
androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}
apply plugin: 'com.google.gms.google-services'

View file

@ -1,13 +1,12 @@
apply plugin: "jacoco"
jacoco {
toolVersion "0.8.7"
reportsDirectory.set(file("$buildDir/reports"))
toolVersion "0.8.5"
reportsDir = file("$buildDir/reports")
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
task jacocoTestReport(type: JacocoReport) {
@ -16,8 +15,8 @@ task jacocoTestReport(type: JacocoReport) {
description = "Generate Jacoco coverage reports"
reports {
xml.required.set(true)
html.required.set(true)
xml.enabled = true
html.enabled = true
}
def excludes = ['**/R.class',

BIN
app/key.p12.gpg Normal file

Binary file not shown.

View file

@ -1,21 +1,33 @@
# General
# Optimizations
-optimizationpasses 5
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-dontobfuscate
-allowaccessmodification
-repackageclasses ''
-verbose
#Config for wulkanowy
#Keep all wulkanowy files
-keep class io.github.wulkanowy.** {*;}
#Config for firebase crashlitycs
#Config for anallitycs
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep class com.crashlytics.** {*;}
-keep public class * extends java.lang.Exception
-dontwarn com.crashlytics.**
#Config for Okio and OkHttp
-dontwarn javax.annotation.**
#Config for OkHttp
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-dontwarn org.codehaus.mojo.animal_sniffer.*
-dontwarn okhttp3.internal.platform.ConscryptPlatform
-dontwarn javax.annotation.**
#Config for MPAndroidChart
@ -24,3 +36,10 @@
#Config for Material Components
-keep class com.google.android.material.tabs.** { *; }
#Config for About Libraries
-keep class .R
-keep class **.R$* {
<fields>;
}

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

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

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

@ -0,0 +1,11 @@
package io.github.wulkanowy.data
import io.github.wulkanowy.utils.DispatchersProvider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
class TestDispatchersProvider : DispatchersProvider() {
override val backgroundThread: CoroutineDispatcher
get() = Dispatchers.Unconfined
}

View file

@ -1,6 +1,5 @@
package io.github.wulkanowy.data.db.migrations
import android.content.Context
import androidx.preference.PreferenceManager
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
@ -9,15 +8,12 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.utils.AppInfo
import org.junit.Rule
abstract class AbstractMigrationTest {
val dbName = "migration-test"
val context: Context get() = ApplicationProvider.getApplicationContext()
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
@ -26,16 +22,12 @@ abstract class AbstractMigrationTest {
)
fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java,
dbName
).addMigrations(
*AppDatabase.getMigrations(
SharedPrefProvider(PreferenceManager.getDefaultSharedPreferences(context)),
AppInfo()
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName)
.addMigrations(*AppDatabase.getMigrations(SharedPrefProvider(PreferenceManager
.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext())))
)
).build()
.build()
// close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database)
return database

View file

@ -2,20 +2,14 @@ package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL
import android.os.Build
import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.assertEquals
@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
@RunWith(AndroidJUnit4::class)
class Migration12Test : AbstractMigrationTest() {
@Test

View file

@ -2,26 +2,17 @@ package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import io.github.wulkanowy.data.db.Converters
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.time.LocalDate.of
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class Migration13Test : AbstractMigrationTest() {
@Test

View file

@ -2,21 +2,12 @@ package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.random.Random
@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class Migration27Test : AbstractMigrationTest() {
@Test

View file

@ -0,0 +1,45 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import java.time.LocalDate.now
import java.time.LocalDateTime
fun getStudent(): Student {
return Student(
email = "test",
password = "test123",
schoolSymbol = "23",
scrapperBaseUrl = "fakelog.cf",
loginType = "AUTO",
isCurrent = true,
userName = "",
studentName = "",
schoolShortName = "",
schoolName = "",
studentId = 0,
classId = 1,
symbol = "",
registrationDate = LocalDateTime.now(),
className = "",
loginMode = "API",
certificateKey = "",
privateKey = "",
mobileBaseUrl = "",
userLoginId = 0,
isParent = false
)
}
fun getSemester() = Semester(
semesterId = 1,
studentId = 1,
classId = 1,
diaryId = 2,
diaryName = "",
end = now(),
schoolYear = 2019,
semesterName = 1,
start = now(),
unitId = 1
)

View file

@ -0,0 +1,83 @@
package io.github.wulkanowy.data.repositories.attendance
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDate.now
import java.time.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class AttendanceLocalTest {
private lateinit var attendanceLocal: AttendanceLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
attendanceLocal = AttendanceLocal(testDb.attendanceDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
getAttendanceEntity(
of(2018, 9, 10),
SentExcuseStatus.ACCEPTED
),
getAttendanceEntity(
of(2018, 9, 14),
SentExcuseStatus.WAITING
),
getAttendanceEntity(
of(2018, 9, 17),
SentExcuseStatus.ACCEPTED
)
)
runBlocking { attendanceLocal.saveAttendance(list) }
val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1)
val attendance = runBlocking { attendanceLocal.getAttendance(semester, of(2018, 9, 10), of(2018, 9, 14)).first() }
assertEquals(2, attendance.size)
assertEquals(attendance[0].date, of(2018, 9, 10))
assertEquals(attendance[1].date, of(2018, 9, 14))
}
private fun getAttendanceEntity(
date: LocalDate,
excuseStatus: SentExcuseStatus
) = Attendance(
studentId = 1,
diaryId = 2,
timeId = 3,
date = date,
number = 0,
subject = "",
name = "",
presence = false,
absence = false,
exemption = false,
lateness = false,
excused = false,
deleted = false,
excusable = false,
excuseStatus = excuseStatus.name
)
}

View file

@ -0,0 +1,59 @@
package io.github.wulkanowy.data.repositories.completedlessons
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDate.now
import java.time.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class CompletedLessonsLocalTest {
private lateinit var completedLessonsLocal: CompletedLessonsLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room
.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
completedLessonsLocal = CompletedLessonsLocal(testDb.completedLessonsDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
getCompletedLesson(of(2018, 9, 10), 1),
getCompletedLesson(of(2018, 9, 14), 2),
getCompletedLesson(of(2018, 9, 17), 3)
)
runBlocking { completedLessonsLocal.saveCompletedLessons(list) }
val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1)
val completed = runBlocking { completedLessonsLocal.getCompletedLessons(semester, of(2018, 9, 10), of(2018, 9, 14)).first() }
assertEquals(2, completed.size)
assertEquals(completed[0].date, of(2018, 9, 10))
assertEquals(completed[1].date, of(2018, 9, 14))
}
private fun getCompletedLesson(date: LocalDate, number: Int): CompletedLesson {
return CompletedLesson(1, 2, date, number, "", "", "", "", "", "", "")
}
}

View file

@ -0,0 +1,52 @@
package io.github.wulkanowy.data.repositories.exam
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate.now
import java.time.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class ExamLocalTest {
private lateinit var examLocal: ExamLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
examLocal = ExamLocal(testDb.examsDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
Exam(1, 2, of(2018, 9, 10), now(), "", "", "", "", "", ""),
Exam(1, 2, of(2018, 9, 14), now(), "", "", "", "", "", ""),
Exam(1, 2, of(2018, 9, 17), now(), "", "", "", "", "", "")
)
runBlocking { examLocal.saveExams(list) }
val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1)
val exams = runBlocking { examLocal.getExams(semester, of(2018, 9, 10), of(2018, 9, 14)).first() }
assertEquals(2, exams.size)
assertEquals(exams[0].date, of(2018, 9, 10))
assertEquals(exams[1].date, of(2018, 9, 14))
}
}

View file

@ -0,0 +1,55 @@
package io.github.wulkanowy.data.repositories.grade
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDate.now
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class GradeLocalTest {
private lateinit var gradeLocal: GradeLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room
.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
createGradeLocal(5, 3.0, LocalDate.of(2018, 9, 10), "", 1),
createGradeLocal(4, 4.0, LocalDate.of(2019, 2, 27), "", 2),
createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2)
)
runBlocking { gradeLocal.saveGrades(list) }
val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1)
val grades = runBlocking { gradeLocal.getGradesDetails(semester).first() }
assertEquals(2, grades.size)
assertEquals(grades[0].date, LocalDate.of(2019, 2, 27))
assertEquals(grades[1].date, LocalDate.of(2019, 2, 28))
}
}

View file

@ -0,0 +1,221 @@
package io.github.wulkanowy.data.repositories.grade
import android.os.Build.VERSION_CODES.P
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate.of
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@SdkSuppress(minSdkVersion = P)
@RunWith(AndroidJUnit4::class)
class GradeRepositoryTest {
@MockK
private lateinit var semesterMock: Semester
private lateinit var studentMock: Student
@MockK
private lateinit var gradeRemote: GradeRemote
private lateinit var gradeLocal: GradeLocal
private lateinit var testDb: AppDatabase
@Before
fun initApi() {
MockKAnnotations.init(this)
testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build()
gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao)
studentMock = getStudentMock()
every { semesterMock.studentId } returns 1
every { semesterMock.diaryId } returns 1
every { semesterMock.schoolYear } returns 2019
every { semesterMock.semesterId } returns 1
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun markOlderThanRegisterDateAsRead() {
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf(
createGradeLocal(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"),
createGradeLocal(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"),
createGradeLocal(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"),
createGradeLocal(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza")
) to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date }
}
assertFalse { grades[0].isRead }
assertFalse { grades[1].isRead }
assertTrue { grades[2].isRead }
assertTrue { grades[3].isRead }
}
@Test
fun mitigateOldGradesNotifications() {
val list = listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Jedna ocena"),
createGradeLocal(4, 4.0, of(2019, 2, 26), "Druga"),
createGradeLocal(3, 5.0, of(2019, 2, 27), "Trzecia")
)
runBlocking { gradeLocal.saveGrades(list) }
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf(
createGradeLocal(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"),
createGradeLocal(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"),
createGradeLocal(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"),
createGradeLocal(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa")
) to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date }
}
assertFalse { grades[0].isRead }
assertFalse { grades[1].isRead }
assertTrue { grades[2].isRead }
assertTrue { grades[3].isRead }
}
@Test
fun subtractLocaleDuplicateGrades() {
val list = listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
)
runBlocking { gradeLocal.saveGrades(list) }
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
) to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!
}
assertEquals(2, grades.first.size)
}
@Test
fun subtractRemoteDuplicateGrades() {
val list = listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
)
runBlocking { gradeLocal.saveGrades(list) }
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
) to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!
}
assertEquals(3, grades.first.size)
}
@Test
fun emptyLocal() {
runBlocking { gradeLocal.saveGrades(listOf()) }
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
) to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!
}
assertEquals(3, grades.first.size)
}
@Test
fun emptyRemote() {
val list = listOf(
createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
)
runBlocking { gradeLocal.saveGrades(list) }
coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (emptyList<Grade>() to emptyList())
val grades = runBlocking {
GradeRepository(gradeLocal, gradeRemote)
.getGrades(studentMock, semesterMock, true)
.filter { it.status == Status.SUCCESS }.first().data!!
}
assertEquals(0, grades.first.size)
}
private fun getStudentMock() = Student(
scrapperBaseUrl = "http://fakelog.cf",
email = "jan@fakelog.cf",
certificateKey = "",
classId = 0,
className = "",
isCurrent = false,
isParent = false,
loginMode = Sdk.Mode.SCRAPPER.name,
loginType = "STANDARD",
mobileBaseUrl = "",
password = "",
privateKey = "",
registrationDate = LocalDateTime.of(2019, 2, 27, 12, 0),
schoolName = "",
schoolShortName = "test",
schoolSymbol = "",
studentId = 0,
studentName = "",
symbol = "",
userLoginId = 0,
userName = ""
)
}

View file

@ -0,0 +1,41 @@
package io.github.wulkanowy.data.repositories.grade
import java.time.LocalDate
import io.github.wulkanowy.sdk.pojo.Grade as GradeRemote
import io.github.wulkanowy.data.db.entities.Grade as GradeLocal
fun createGradeLocal(value: Int, weight: Double, date: LocalDate, desc: String, semesterId: Int = 1): GradeLocal {
return GradeLocal(
semesterId = semesterId,
studentId = 1,
modifier = .0,
teacher = "",
subject = "",
date = date,
color = "",
comment = "",
description = desc,
entry = "",
gradeSymbol = "",
value = value.toDouble(),
weight = "",
weightValue = weight
)
}
fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String): GradeRemote {
return GradeRemote(
subject = "",
color = "",
comment = "",
date = date,
description = desc,
entry = "",
modifier = .0,
symbol = "",
teacher = "",
value = value.toDouble(),
weight = weight.toString(),
weightValue = weight
)
}

View file

@ -0,0 +1,112 @@
package io.github.wulkanowy.data.repositories.gradestatistics
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate.now
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class GradeStatisticsLocalTest {
private lateinit var gradeStatisticsLocal: GradeStatisticsLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndRead_subject() {
val list = listOf(
getGradeStatistics("Matematyka", 2, 1),
getGradeStatistics("Fizyka", 1, 2)
)
runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() }
assertEquals(1, stats.size)
assertEquals(stats[0].subject, "Matematyka")
}
@Test
fun saveAndRead_all() {
val list = listOf(
getGradeStatistics("Matematyka", 2, 1),
getGradeStatistics("Chemia", 2, 1),
getGradeStatistics("Fizyka", 1, 2)
)
runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() }
assertEquals(2, stats.size)
// assertEquals(3, stats.size)
// assertEquals(stats[0].subject, "Wszystkie") // now in main repo
assertEquals(stats[0].subject, "Matematyka")
assertEquals(stats[1].subject, "Chemia")
}
@Test
fun saveAndRead_points() {
val list = listOf(
getGradePointsStatistics("Matematyka", 2, 1),
getGradePointsStatistics("Chemia", 2, 1),
getGradePointsStatistics("Fizyka", 1, 2)
)
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() }
with(stats[0]) {
assertEquals(subject, "Matematyka")
assertEquals(others, 5.0)
assertEquals(student, 5.0)
}
}
@Test
fun saveAndRead_subjectEmpty() {
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() }
assertEquals(emptyList(), stats)
}
@Test
fun saveAndRead_allEmpty() {
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() }
assertEquals(emptyList(), stats)
}
private fun getSemester(): Semester {
return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1)
}
private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics {
return GradeStatistics(studentId, semesterId, subject, 5, 5, false)
}
private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics {
return GradePointsStatistics(studentId, semesterId, subject, 5.0, 5.0)
}
}

View file

@ -0,0 +1,51 @@
package io.github.wulkanowy.data.repositories.luckynumber
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDateTime.now
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class LuckyNumberLocalTest {
private lateinit var luckyNumberLocal: LuckyNumberLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room
.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
luckyNumberLocal = LuckyNumberLocal(testDb.luckyNumberDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val number = LuckyNumber(1, LocalDate.of(2019, 1, 20), 14)
runBlocking { luckyNumberLocal.saveLuckyNumber(number) }
val student = Student("", "", "", "", "", "", false, "", "", "", 1, 1, "", "", "", "", "", "", 1, false, now())
val luckyNumber = runBlocking { luckyNumberLocal.getLuckyNumber(student, LocalDate.of(2019, 1, 20)).first() }
assertEquals(1, luckyNumber?.studentId)
assertEquals(LocalDate.of(2019, 1, 20), luckyNumber?.date)
assertEquals(14, luckyNumber?.luckyNumber)
}
}

View file

@ -0,0 +1,65 @@
package io.github.wulkanowy.data.repositories.recipient
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDateTime
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class RecipientLocalTest {
private lateinit var recipientLocal: RecipientLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
recipientLocal = RecipientLocal(testDb.recipientDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
Recipient(1, "2rPracownik", "Kowalski Jan", "Kowalski Jan [KJ] - Pracownik (Fake123456)", 3, 4, 2, "hash"),
Recipient(1, "3rPracownik", "Kowalska Karolina", "Kowalska Karolina [KK] - Pracownik (Fake123456)", 4, 4, 2, "hash"),
Recipient(1, "4rPracownik", "Krupa Stanisław", "Krupa Stanisław [KS] - Uczeń (Fake123456)", 5, 4, 1, "hash")
)
runBlocking { recipientLocal.saveRecipients(list) }
val student = Student("fakelog.cf", "AUTO", "", "", "", "", false, "", "", "", 1, 0, "", "", "", "", "", "", 1, true, LocalDateTime.now())
val recipients = runBlocking {
recipientLocal.getRecipients(
student = student,
role = 2,
unit = ReportingUnit(1, 4, "", 0, "", emptyList())
)
}
assertEquals(2, recipients.size)
assertEquals(1, recipients[0].studentId)
assertEquals("3rPracownik", recipients[1].realId)
assertEquals("Kowalski Jan", recipients[0].name)
assertEquals("Kowalska Karolina [KK] - Pracownik (Fake123456)", recipients[1].realName)
assertEquals(3, recipients[0].loginId)
assertEquals(4, recipients[1].unitId)
assertEquals(2, recipients[0].role)
assertEquals("hash", recipients[1].hash)
}
}

View file

@ -0,0 +1,46 @@
package io.github.wulkanowy.data.repositories.student
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.TestDispatchersProvider
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.repositories.getStudent
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class StudentLocalTest {
private lateinit var studentLocal: StudentLocal
private lateinit var testDb: AppDatabase
private val student = getStudent()
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
studentLocal = StudentLocal(testDb.studentDao, TestDispatchersProvider(), context)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
runBlocking { studentLocal.saveStudents(listOf(student)) }
val student = runBlocking { studentLocal.getCurrentStudent(true) }
assertEquals("23", student?.schoolSymbol)
}
}

View file

@ -0,0 +1,48 @@
package io.github.wulkanowy.data.repositories.timetable
import java.time.LocalDateTime
import java.time.LocalDateTime.now
import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal {
return TimetableLocal(
studentId = 1,
diaryId = 2,
number = number,
start = start,
end = now(),
date = start.toLocalDate(),
subject = subject,
subjectOld = "",
group = "",
room = room,
roomOld = "",
teacher = teacher,
teacherOld = "",
info = "",
isStudentPlan = true,
changes = changes,
canceled = false
)
}
fun createTimetableRemote(start: LocalDateTime, number: Int = 1, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableRemote {
return TimetableRemote(
number = number,
start = start,
end = start.plusMinutes(45),
date = start.toLocalDate(),
subject = subject,
group = "",
room = room,
teacher = teacher,
info = "",
changes = changes,
canceled = false,
roomOld = "",
subjectOld = "",
teacherOld = "",
studentPlan = true
)
}

View file

@ -0,0 +1,59 @@
package io.github.wulkanowy.data.repositories.timetable
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDateTime.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class TimetableLocalTest {
private lateinit var timetableDb: TimetableLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
timetableDb = TimetableLocal(testDb.timetableDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
val list = listOf(
createTimetableLocal(of(2018, 9, 10, 0, 0, 0), 1),
createTimetableLocal(of(2018, 9, 14, 0, 0, 0), 1),
createTimetableLocal(of(2018, 9, 17, 0, 0, 0), 1)
)
runBlocking { timetableDb.saveTimetable(list) }
val semester = Semester(1, 2, "", 1, 1, 2019, LocalDate.now(), LocalDate.now(), 1, 1)
val exams = runBlocking {
timetableDb.getTimetable(
semester = semester,
startDate = LocalDate.of(2018, 9, 10),
endDate = LocalDate.of(2018, 9, 14)
).first()
}
assertEquals(2, exams.size)
assertEquals(exams[0].date, LocalDate.of(2018, 9, 10))
assertEquals(exams[1].date, LocalDate.of(2018, 9, 14))
}
}

View file

@ -0,0 +1,155 @@
package io.github.wulkanowy.data.repositories.timetable
import android.os.Build.VERSION_CODES.P
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.repositories.getSemester
import io.github.wulkanowy.data.repositories.getStudent
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate
import java.time.LocalDateTime.of
import kotlin.test.assertEquals
@SdkSuppress(minSdkVersion = P)
@RunWith(AndroidJUnit4::class)
class TimetableRepositoryTest {
@MockK(relaxed = true)
private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper
@MockK
private lateinit var timetableRemote: TimetableRemote
private lateinit var timetableLocal: TimetableLocal
private lateinit var testDb: AppDatabase
private val student = getStudent()
private val semester = getSemester()
@Before
fun initApi() {
MockKAnnotations.init(this)
testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build()
timetableLocal = TimetableLocal(testDb.timetableDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun copyRoomToCompletedFromPrevious() {
runBlocking {
timetableLocal.saveTimetable(listOf(
createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "123", "Przyroda"),
createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "321", "Religia"),
createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "213", "W-F"),
createTimetableLocal(of(2019, 3, 5, 10, 30), 3, "213", "W-F", "Jan Kowalski")
))
}
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf(
createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"),
createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "", "Religia"),
createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "", "W-F"),
createTimetableLocal(of(2019, 3, 5, 10, 30), 4, "", "W-F")
)
val lessons = runBlocking {
TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable(
student = student,
semester = semester,
start = LocalDate.of(2019, 3, 5),
end = LocalDate.of(2019, 3, 5),
forceRefresh = true
).filter { it.status == Status.SUCCESS }.first().data.orEmpty()
}
assertEquals(4, lessons.size)
assertEquals("123", lessons[0].room)
assertEquals("321", lessons[1].room)
assertEquals("213", lessons[2].room)
}
@Test
fun copyTeacherToCompletedFromPrevious() {
val list = listOf(
createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false),
createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Paweł Poniedziałkowski", false),
createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Wtorkowska", true),
createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true),
createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "Joanna Wtorkowska", false),
createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "Joanna Wtorkowska", false),
createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "Joanna Środowska", true),
createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "Joanna Środowska", true),
createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "", false),
createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "", false),
createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "", true),
createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "", true)
)
runBlocking { timetableLocal.saveTimetable(list) }
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf(
createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false),
createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true),
createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false),
createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true),
createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "", false),
createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "", true),
createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "", false),
createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "", true),
createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false),
createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true),
createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false),
createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true)
)
val lessons = runBlocking {
TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable(
student = student,
semester = semester,
start = LocalDate.of(2019, 12, 23),
end = LocalDate.of(2019, 12, 25),
forceRefresh = true
).filter { it.status == Status.SUCCESS }.first().data.orEmpty()
}
assertEquals(12, lessons.size)
assertEquals("Paweł Poniedziałkowski", lessons[0].teacher)
assertEquals("Jakub Wtorkowski", lessons[1].teacher)
assertEquals("Joanna Poniedziałkowska", lessons[2].teacher)
assertEquals("Joanna Wtorkowska", lessons[3].teacher)
assertEquals("Joanna Wtorkowska", lessons[4].teacher)
assertEquals("", lessons[5].teacher)
assertEquals("", lessons[6].teacher)
assertEquals("", lessons[7].teacher)
assertEquals("Paweł Środowski", lessons[8].teacher)
assertEquals("Paweł Czwartkowski", lessons[9].teacher)
assertEquals("Paweł Środowski", lessons[10].teacher)
assertEquals("Paweł Czwartkowski", lessons[11].teacher)
}
}

View file

@ -1,33 +0,0 @@
{
"agcgw":{
"backurl":"connect-dre.dbankcloud.cn",
"url":"connect-dre.hispace.hicloud.com"
},
"client":{
"cp_id":"890048000024105546",
"product_id":"",
"client_id":"",
"client_secret":"",
"app_id":"101440411",
"package_name":"io.github.wulkanowy.dev",
"api_key":""
},
"service":{
"analytics":{
"collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"resource_id":"p1",
"channel_id":""
},
"search":{
"url":"https://search-dre.cloud.huawei.com"
},
"cloudstorage":{
"storage_url":"https://ops-dre.agcstorage.link"
},
"ml":{
"mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
}
},
"region":"DE",
"configuration_version":"1.0"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

View file

@ -1,19 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="#FFF"
android:pathData="M3.9512,2A2,2 0,0 0,2 4L2,18A2,2 0,0 0,4 20L10.0996,20C11.3596,21.24 13.09,22 15,22A7,7 0,0 0,15.7988 21.9551L15.7988,19.7832A4.85,4.85 0,0 1,15 19.8496C12.32,19.8496 10.1504,17.68 10.1504,15A4.85,4.85 0,0 1,15 10.1504C17.4677,10.1504 19.4978,11.9912 19.8047,14.375C20.566,14.3758 21.3108,14.5325 21.9922,14.834C21.9491,12.9905 21.2036,11.3226 20,10.0996L20,4A2,2 0,0 0,18 2L4,2A2,2 0,0 0,3.9512 2zM4,5L10,5L10,8L4,8L4,5zM12,5L18,5L18,8L12,8L12,5zM4,10L10.0996,10C9.2596,10.82 8.6291,11.85 8.2891,13L4,13L4,10zM14,12L14,15.6895L15.7988,16.7266L15.7988,14.9922L15.5,14.8203L15.5,12L14,12zM4,15L8,15C8,16.07 8.2399,17.09 8.6699,18L4,18L4,15z" />
<path
android:fillColor="#FFF"
android:pathData="m17.298,24v-8.1249h2.5c0.7143,0 1.3523,0.1618 1.9141,0.4855 0.5655,0.3199 1.0063,0.7775 1.3225,1.3728 0.3162,0.5915 0.4743,1.2649 0.4743,2.0201v0.3739c0,0.7552 -0.1562,1.4267 -0.4687,2.0145 -0.3088,0.5878 -0.7459,1.0435 -1.3114,1.3672C21.1633,23.8326 20.5253,23.9963 19.8148,24ZM18.9721,17.2311v5.4241h0.8091c0.6548,0 1.1551,-0.2139 1.5011,-0.6417 0.346,-0.4278 0.5227,-1.0398 0.5301,-1.8359v-0.4297c0,-0.8259 -0.1711,-1.4509 -0.5134,-1.875 -0.3423,-0.4278 -0.8426,-0.6417 -1.5011,-0.6417z" />
</group>
</vector>

View file

@ -8,6 +8,6 @@ open class TimberTreeNoOp : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {}
}
class CrashLogTree : TimberTreeNoOp()
class CrashlyticsTree : TimberTreeNoOp()
class CrashLogExceptionTree : TimberTreeNoOp()
class CrashlyticsExceptionTree : TimberTreeNoOp()

View file

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

View file

@ -1,18 +0,0 @@
package io.github.wulkanowy.utils
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Singleton
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER", "unused")
@Singleton
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
fun showInAppReview(activity: MainActivity) {
// do nothing
}
}

View file

@ -1,39 +0,0 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
private val analytics by lazy { HiAnalytics.getInstance(context) }
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply {
params.forEach {
if (it.second == null) return@forEach
when (it.second) {
is String, is String? -> putString(it.first, it.second as String)
is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
}
}
analytics.onEvent(name, this)
}
}
fun setCurrentScreen(activity: Activity, name: String?) {
analytics.pageStart(name, activity::class.simpleName)
}
fun popCurrentScreen(name: String?) {
analytics.pageEnd(name)
}
}

View file

@ -1,37 +0,0 @@
package io.github.wulkanowy.utils
import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
private val connectCrash by lazy { AGConnectCrash.getInstance() }
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
connectCrash.log(format(priority, tag, message))
}
}
class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter) {
private val connectCrash by lazy { AGConnectCrash.getInstance() }
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
// 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) {
connectCrash.recordException(t)
} else {
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
}*/
}
}

View file

@ -1,17 +0,0 @@
package io.github.wulkanowy.utils
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
fun showInAppReview(activity: MainActivity) {
// do nothing
}
}

View file

@ -1,17 +0,0 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.view.View
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER")
class UpdateHelper @Inject constructor() {
lateinit var messageContainer: View
fun checkAndInstallUpdates(activity: Activity) {}
fun onActivityResult(requestCode: Int, resultCode: Int) {}
fun onResume(activity: Activity) {}
}

View file

@ -6,47 +6,21 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="mailto" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="geo" />
</intent>
</queries>
<application
android:name=".WulkanowyApp"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/WulkanowyTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<activity
android:name=".ui.modules.splash.SplashActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/WulkanowyTheme.SplashScreen"
tools:ignore="LockedOrientationActivity">
@ -59,7 +33,7 @@
android:name=".ui.modules.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:label="@string/login_title"
android:theme="@style/WulkanowyTheme.Login"
android:theme="@style/WulkanowyTheme.NoActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.modules.main.MainActivity"
@ -71,12 +45,11 @@
android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize"
android:label="@string/send_message_title"
android:theme="@style/WulkanowyTheme.MessageSend"
android:theme="@style/WulkanowyTheme.NoActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
android:excludeFromRecents="true"
android:exported="true"
android:noHistory="true"
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
<intent-filter>
@ -86,7 +59,6 @@
<activity
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
android:excludeFromRecents="true"
android:exported="true"
android:noHistory="true"
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
<intent-filter>
@ -97,23 +69,6 @@
<service
android:name=".services.widgets.TimetableWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".services.piggyback.VulcanNotificationListenerService"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<service
android:name=".services.messaging.AppMessagingService"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
@ -128,7 +83,6 @@
</receiver>
<receiver
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
android:exported="true"
android:label="@string/lucky_number_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -141,9 +95,11 @@
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
@ -160,38 +116,34 @@
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
android:enabled="${firebase_enabled}"
android:exported="false"
tools:ignore="MissingClass" />
android:exported="false" />
<meta-data
android:name="install_channel"
android:value="${install_channel}" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_all" />
android:resource="@drawable/ic_stat_push" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="push_channel" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="${admob_project_id}" />
<meta-data
android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
android:value="true" />
</application>
</manifest>

View file

@ -33,22 +33,6 @@
},
{
"displayName": "Mateusz Idziejczak",
"githubUsername": "Luncenok"
},
{
"displayName": "Daniel Olczyk",
"githubUsername": "MRmlik12"
},
{
"displayName": "Damian Czupryn",
"githubUsername": "Daxxxis"
},
{
"displayName": "Kamil Studziński",
"githubUsername": "studzinskik"
},
{
"displayName": "Tomasz F.",
"githubUsername": "Pengwius"
"githubUsername": "PanTajemnic"
}
]

View file

@ -1,22 +1,21 @@
package io.github.wulkanowy
import android.app.Application
import android.content.Context
import android.util.Log.DEBUG
import android.util.Log.INFO
import android.util.Log.VERBOSE
import android.webkit.WebView
import androidx.hilt.work.HiltWorkerFactory
import androidx.multidex.MultiDex
import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver
import dagger.hilt.android.HiltAndroidApp
import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.CrashlyticsExceptionTree
import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree
import timber.log.Timber
import javax.inject.Inject
@ -33,58 +32,36 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
@Inject
lateinit var analyticsHelper: AnalyticsHelper
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
initializeAppLanguage()
Lingver.init(this)
themeManager.applyDefaultTheme()
initLogging()
fixWebViewLocale()
}
private fun initLogging() {
if (appInfo.isDebug) {
Timber.plant(DebugLogTree())
Timber.plant(
FileLoggerTree.Builder()
.withFileName("wulkanowy.%g.log")
.withDirName(applicationContext.filesDir.absolutePath)
.withFileLimit(10)
.withMinPriority(DEBUG)
.build()
Timber.plant(FileLoggerTree.Builder()
.withFileName("wulkanowy.%g.log")
.withDirName(applicationContext.filesDir.absolutePath)
.withFileLimit(10)
.withMinPriority(DEBUG)
.build()
)
} else {
Timber.plant(CrashLogExceptionTree())
Timber.plant(CrashLogTree())
Timber.plant(CrashlyticsExceptionTree())
Timber.plant(CrashlyticsTree())
}
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
}
private fun initializeAppLanguage() {
Lingver.init(this)
if (preferencesRepository.appLanguage == "system") {
Lingver.getInstance().setFollowSystemLocale(this)
analyticsHelper.logEvent("language", "startup" to appInfo.systemLanguage)
} else {
analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage)
}
}
private fun fixWebViewLocale() {
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
try {
WebView(this).destroy()
} catch (e: Throwable) {
//Ignore exceptions
}
}
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)

View file

@ -1,245 +0,0 @@
package io.github.wulkanowy.data
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.create
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal class DataModule {
@Singleton
@Provides
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
setSimpleHttpLogger { Timber.d(it) }
// for debug only
addInterceptor(chuckerInterceptor, network = true)
}
@Singleton
@Provides
fun provideChuckerCollector(
@ApplicationContext context: Context,
prefRepository: PreferencesRepository
) = ChuckerCollector(
context = context,
showNotification = prefRepository.isDebugNotificationEnable,
retentionPeriod = RetentionManager.Period.ONE_HOUR
)
@Singleton
@Provides
fun provideChuckerInterceptor(
@ApplicationContext context: Context,
chuckerCollector: ChuckerCollector
) = ChuckerInterceptor.Builder(context)
.collector(chuckerCollector)
.alwaysReadResponseBody(true)
.build()
@Singleton
@Provides
fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addNetworkInterceptor(chuckerInterceptor)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
@OptIn(ExperimentalSerializationApi::class)
@Singleton
@Provides
fun provideRetrofit(
okHttpClient: OkHttpClient,
json: Json,
appInfo: AppInfo
): Retrofit = Retrofit.Builder()
.baseUrl(appInfo.messagesBaseUrl)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
@Singleton
@Provides
fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
@Singleton
@Provides
fun provideDatabase(
@ApplicationContext context: Context,
sharedPrefProvider: SharedPrefProvider,
appInfo: AppInfo
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
@Singleton
@Provides
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
@Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
FlowSharedPreferences(sharedPreferences)
@Singleton
@Provides
fun provideJson() = Json {
ignoreUnknownKeys = true
}
@Singleton
@Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao
@Singleton
@Provides
fun provideSemesterDao(database: AppDatabase) = database.semesterDao
@Singleton
@Provides
fun provideGradeDao(database: AppDatabase) = database.gradeDao
@Singleton
@Provides
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
@Singleton
@Provides
fun provideGradePartialStatisticsDao(database: AppDatabase) = database.gradePartialStatisticsDao
@Singleton
@Provides
fun provideGradeSemesterStatisticsDao(database: AppDatabase) =
database.gradeSemesterStatisticsDao
@Singleton
@Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatisticsDao
@Singleton
@Provides
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
@Singleton
@Provides
fun provideMessageAttachmentsDao(database: AppDatabase) = database.messageAttachmentDao
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao
@Singleton
@Provides
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao
@Singleton
@Provides
fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao
@Singleton
@Provides
fun provideTimetableDao(database: AppDatabase) = database.timetableDao
@Singleton
@Provides
fun provideNoteDao(database: AppDatabase) = database.noteDao
@Singleton
@Provides
fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao
@Singleton
@Provides
fun provideSubjectDao(database: AppDatabase) = database.subjectDao
@Singleton
@Provides
fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao
@Singleton
@Provides
fun provideCompletedLessonsDao(database: AppDatabase) = database.completedLessonsDao
@Singleton
@Provides
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao
@Singleton
@Provides
fun provideRecipientDao(database: AppDatabase) = database.recipientDao
@Singleton
@Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
@Singleton
@Provides
fun provideTeacherDao(database: AppDatabase) = database.teacherDao
@Singleton
@Provides
fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao
@Singleton
@Provides
fun provideConferenceDao(database: AppDatabase) = database.conferenceDao
@Singleton
@Provides
fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao
@Singleton
@Provides
fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao
@Singleton
@Provides
fun provideTimetableHeaderDao(database: AppDatabase) = database.timetableHeaderDao
@Singleton
@Provides
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
@Singleton
@Provides
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
@Singleton
@Provides
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
}

View file

@ -0,0 +1,157 @@
package io.github.wulkanowy.data
import android.content.Context
import android.content.SharedPreferences
import android.content.res.AssetManager
import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk
import timber.log.Timber
import javax.inject.Singleton
@Module
@InstallIn(ApplicationComponent::class)
internal class RepositoryModule {
@Singleton
@Provides
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
return Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
setSimpleHttpLogger { Timber.d(it) }
// for debug only
addInterceptor(ChuckerInterceptor(
context = context,
collector = chuckerCollector,
alwaysReadResponseBody = true
), true)
}
}
@Singleton
@Provides
fun provideChuckerCollector(@ApplicationContext context: Context, prefRepository: PreferencesRepository): ChuckerCollector {
return ChuckerCollector(
context = context,
showNotification = prefRepository.isDebugNotificationEnable,
retentionPeriod = RetentionManager.Period.ONE_HOUR
)
}
@Singleton
@Provides
fun provideDatabase(@ApplicationContext context: Context, sharedPrefProvider: SharedPrefProvider) = AppDatabase.newInstance(context, sharedPrefProvider)
@Singleton
@Provides
fun provideResources(@ApplicationContext context: Context): Resources = context.resources
@Singleton
@Provides
fun provideAssets(@ApplicationContext context: Context): AssetManager = context.assets
@Singleton
@Provides
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@Singleton
@Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao
@Singleton
@Provides
fun provideSemesterDao(database: AppDatabase) = database.semesterDao
@Singleton
@Provides
fun provideGradeDao(database: AppDatabase) = database.gradeDao
@Singleton
@Provides
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
@Singleton
@Provides
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics
@Singleton
@Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics
@Singleton
@Provides
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
@Singleton
@Provides
fun provideMessageAttachmentsDao(database: AppDatabase) = database.messageAttachmentDao
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao
@Singleton
@Provides
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao
@Singleton
@Provides
fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao
@Singleton
@Provides
fun provideTimetableDao(database: AppDatabase) = database.timetableDao
@Singleton
@Provides
fun provideNoteDao(database: AppDatabase) = database.noteDao
@Singleton
@Provides
fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao
@Singleton
@Provides
fun provideSubjectDao(database: AppDatabase) = database.subjectDao
@Singleton
@Provides
fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao
@Singleton
@Provides
fun provideCompletedLessonsDao(database: AppDatabase) = database.completedLessonsDao
@Singleton
@Provides
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao
@Singleton
@Provides
fun provideRecipientDao(database: AppDatabase) = database.recipientDao
@Singleton
@Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
@Singleton
@Provides
fun provideTeacherDao(database: AppDatabase) = database.teacherDao
@Singleton
@Provides
fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao
}

View file

@ -1,12 +0,0 @@
package io.github.wulkanowy.data.api
import io.github.wulkanowy.data.db.entities.AdminMessage
import retrofit2.http.GET
import javax.inject.Singleton
@Singleton
interface AdminMessageService {
@GET("/v1.json")
suspend fun getAdminMessages(): List<AdminMessage>
}

View file

@ -6,16 +6,14 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import androidx.room.migration.Migration
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.GradeStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
@ -23,29 +21,21 @@ 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.GradeStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
@ -53,19 +43,14 @@ 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.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
@ -85,31 +70,13 @@ import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
import io.github.wulkanowy.data.db.migrations.Migration37
import io.github.wulkanowy.data.db.migrations.Migration38
import io.github.wulkanowy.data.db.migrations.Migration39
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration40
import io.github.wulkanowy.data.db.migrations.Migration41
import io.github.wulkanowy.data.db.migrations.Migration42
import io.github.wulkanowy.data.db.migrations.Migration43
import io.github.wulkanowy.data.db.migrations.Migration44
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@Singleton
@ -123,9 +90,8 @@ import javax.inject.Singleton
AttendanceSummary::class,
Grade::class,
GradeSummary::class,
GradePartialStatistics::class,
GradeStatistics::class,
GradePointsStatistics::class,
GradeSemesterStatistics::class,
Message::class,
MessageAttachment::class,
Note::class,
@ -137,14 +103,7 @@ import javax.inject.Singleton
Recipient::class,
MobileDevice::class,
Teacher::class,
School::class,
Conference::class,
TimetableAdditional::class,
StudentInfo::class,
TimetableHeader::class,
SchoolAnnouncement::class,
Notification::class,
AdminMessage::class
School::class
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -153,64 +112,47 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 44
const val VERSION_SCHEMA = 27
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15(),
Migration16(),
Migration17(),
Migration18(),
Migration19(sharedPrefProvider),
Migration20(),
Migration21(),
Migration22(),
Migration23(),
Migration24(),
Migration25(),
Migration26(),
Migration27(),
Migration28(),
Migration29(),
Migration30(),
Migration31(),
Migration32(),
Migration33(),
Migration34(),
Migration35(appInfo),
Migration36(),
Migration37(),
Migration38(),
Migration39(),
Migration40(),
Migration41(sharedPrefProvider),
Migration42(),
Migration43(),
Migration44()
)
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf(
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15(),
Migration16(),
Migration17(),
Migration18(),
Migration19(sharedPrefProvider),
Migration20(),
Migration21(),
Migration22(),
Migration23(),
Migration24(),
Migration25(),
Migration26(),
Migration27(),
)
}
fun newInstance(
context: Context,
sharedPrefProvider: SharedPrefProvider,
appInfo: AppInfo
) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE)
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
.fallbackToDestructiveMigrationOnDowngrade()
.addMigrations(*getMigrations(sharedPrefProvider, appInfo))
.build()
fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE)
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
.fallbackToDestructiveMigrationOnDowngrade()
.addMigrations(*getMigrations(sharedPrefProvider))
.build()
}
}
abstract val studentDao: StudentDao
@ -229,11 +171,9 @@ abstract class AppDatabase : RoomDatabase() {
abstract val gradeSummaryDao: GradeSummaryDao
abstract val gradePartialStatisticsDao: GradePartialStatisticsDao
abstract val gradeStatistics: GradeStatisticsDao
abstract val gradePointsStatisticsDao: GradePointsStatisticsDao
abstract val gradeSemesterStatisticsDao: GradeSemesterStatisticsDao
abstract val gradePointsStatistics: GradePointsStatisticsDao
abstract val messagesDao: MessagesDao
@ -258,18 +198,4 @@ abstract class AppDatabase : RoomDatabase() {
abstract val teacherDao: TeacherDao
abstract val schoolDao: SchoolDao
abstract val conferenceDao: ConferenceDao
abstract val timetableAdditionalDao: TimetableAdditionalDao
abstract val studentInfoDao: StudentInfoDao
abstract val timetableHeaderDao: TimetableHeaderDao
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
abstract val notificationDao: NotificationDao
abstract val adminMessagesDao: AdminMessageDao
}

View file

@ -1,10 +1,9 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
@ -14,7 +13,15 @@ import java.util.Date
class Converters {
private val json = Json
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
private val integerListAdapter by lazy {
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
}
private val stringListPairAdapter by lazy {
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
}
@TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run {
@ -44,25 +51,21 @@ class Converters {
@TypeConverter
fun intListToJson(list: List<Int>): String {
return json.encodeToString(list)
return integerListAdapter.toJson(list)
}
@TypeConverter
fun jsonToIntList(value: String): List<Int> {
return json.decodeFromString(value)
return integerListAdapter.fromJson(value).orEmpty()
}
@TypeConverter
fun stringPairListToJson(list: List<Pair<String, String>>): String {
return json.encodeToString(list)
return stringListPairAdapter.toJson(list)
}
@TypeConverter
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
return try {
json.decodeFromString(value)
} catch (e: SerializationException) {
emptyList() // handle errors from old gson Pair serialized data
}
return stringListPairAdapter.fromJson(value).orEmpty()
}
}

View file

@ -6,9 +6,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SharedPrefProvider @Inject constructor(
private val sharedPref: SharedPreferences
) {
class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) {
companion object {
const val APP_VERSION_CODE_KEY = "app_version_code"
@ -20,18 +18,9 @@ class SharedPrefProvider @Inject constructor(
fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)
fun getString(key: String) = sharedPref.getString(key, null)
fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
fun getString(key: String, defaultValue: String): String =
sharedPref.getString(key, defaultValue) ?: defaultValue
fun getBoolean(key: String, defaultValue: Boolean): Boolean =
sharedPref.getBoolean(key, defaultValue)
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) =
sharedPref.edit(sync) { putBoolean(key, value) }
fun putString(key: String, value: String?, sync: Boolean = false) {
fun putString(key: String, value: String, sync: Boolean = false) {
sharedPref.edit(sync) { putString(key, value) }
}

View file

@ -0,0 +1,68 @@
package io.github.wulkanowy.data.db.adapters
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
object PairAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (type !is ParameterizedType || List::class.java != type.rawType) return null
if (type.actualTypeArguments[0] != Pair::class.java) return null
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
return PairAdapter(listAdapter, mapAdapter)
}
private class PairAdapter(
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
private val mapAdapter: JsonAdapter<Map<String, String>>,
) : JsonAdapter<List<Pair<String, String>>>() {
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
writer.beginArray()
value?.forEach {
writer.beginObject()
writer.name("first").value(it.first)
writer.name("second").value(it.second)
writer.endObject()
}
writer.endArray()
}
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
else deserializeGsonPair(reader)
}
// for compatibility with 0.21.0
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
val map = mapAdapter.fromJson(reader) ?: return null
return map.entries.map {
it.key to it.value
}
}
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
val list = listAdapter.fromJson(reader) ?: return null
return list.map {
require(it.size == 2) {
"pair with more or less than two elements: $list"
}
it["first"].orEmpty() to it["second"].orEmpty()
}
}
}
}

View file

@ -1,25 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.AdminMessage
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
abstract class AdminMessageDao : BaseDao<AdminMessage> {
@Query("SELECT * FROM AdminMessages")
abstract fun loadAll(): Flow<List<AdminMessage>>
@Transaction
open suspend fun removeOldAndSaveNew(
oldMessages: List<AdminMessage>,
newMessages: List<AdminMessage>
) {
deleteAll(oldMessages)
insertAll(newMessages)
}
}

View file

@ -11,11 +11,6 @@ import javax.inject.Singleton
@Dao
interface AttendanceDao : BaseDao<Attendance> {
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
fun loadAll(
diaryId: Int,
studentId: Int,
start: LocalDate,
end: LocalDate
): Flow<List<Attendance>>
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Attendance>>
}

View file

@ -12,5 +12,5 @@ import javax.inject.Singleton
interface CompletedLessonsDao : BaseDao<CompletedLesson> {
@Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(studentId: Int, diaryId: Int, from: LocalDate, end: LocalDate): Flow<List<CompletedLesson>>
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<CompletedLesson>>
}

View file

@ -1,16 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Conference
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
import javax.inject.Singleton
@Dao
@Singleton
interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>>
}

View file

@ -1,13 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import kotlinx.coroutines.flow.Flow
@Dao
interface GradePartialStatisticsDao : BaseDao<GradePartialStatistics> {
@Query("SELECT * FROM GradePartialStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradePartialStatistics>>
}

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