forked from github/wulkanowy-mirror
Compare commits
294 Commits
feature/at
...
develop
Author | SHA1 | Date | |
---|---|---|---|
4316d4ba10 | |||
5e78b01415 | |||
![]() |
528a947ffd | ||
![]() |
ca9caeca66 | ||
![]() |
e971eb7821 | ||
7266d1fcd3 | |||
e048fe4c35 | |||
49541e9a7d | |||
72c93594f6 | |||
b249eab1eb | |||
![]() |
c7afa262d9 | ||
![]() |
b622c09e56 | ||
![]() |
87fb1916d8 | ||
![]() |
5ca9ac3978 | ||
![]() |
c76ace40eb | ||
![]() |
4a585fc56e | ||
![]() |
f3afe7fdb7 | ||
![]() |
859c6ef154 | ||
![]() |
24abe47332 | ||
![]() |
52c1878f6b | ||
![]() |
4060240368 | ||
![]() |
f69816fbac | ||
![]() |
1a41e9e3ee | ||
![]() |
1c0df6c145 | ||
![]() |
2b61e883c5 | ||
d29d95bfae | |||
65a0457675 | |||
b25c61a485 | |||
e33350e153 | |||
8109459ee2 | |||
![]() |
31a7ae6d15 | ||
![]() |
4b3b4a21fa | ||
![]() |
9a6b17c9d9 | ||
![]() |
729e0f547b | ||
![]() |
faa8d34e79 | ||
e553d9cdcf | |||
![]() |
b6f5ac91ad | ||
![]() |
d1d0caa1e3 | ||
eed3a93196 | |||
![]() |
c40779f48f | ||
![]() |
594d2dbec5 | ||
f6b8c33121 | |||
7b728d910e | |||
7302bb3336 | |||
5886ed2bce | |||
3fdd92ea35 | |||
708e25c622 | |||
36d9e02de7 | |||
ebe6bf1cc0 | |||
4bd95237c6 | |||
cb553b1a33 | |||
f5a95421a1 | |||
f2857bdece | |||
3c1497394c | |||
![]() |
26596e8254 | ||
![]() |
6e472d6c5c | ||
9f6ae0e6b5 | |||
![]() |
6f4826249c | ||
01fa007c89 | |||
3f7db44ea7 | |||
c426701b27 | |||
2d8db2b071 | |||
01f6f402b4 | |||
e058134740 | |||
0aefab9463 | |||
748e9f62f3 | |||
c40e371843 | |||
484996120e | |||
2de3362e0b | |||
470827fc42 | |||
6a524deca0 | |||
f9fcfe6d1a | |||
02f8b45ad5 | |||
6ccb2e9b5c | |||
cbf405fb16 | |||
![]() |
2f3e1b6aae | ||
![]() |
567d868f76 | ||
![]() |
12030efee2 | ||
![]() |
04c382643d | ||
58f7c84cd5 | |||
a2e641523a | |||
3b2eed487a | |||
bc999f1b9c | |||
![]() |
fc140ad9c1 | ||
![]() |
78a2cc89e9 | ||
![]() |
dbfe5c8918 | ||
![]() |
c697ca7ad1 | ||
![]() |
1b00f4e518 | ||
![]() |
fb77bf882f | ||
![]() |
466ebbef3a | ||
![]() |
458a4c8164 | ||
![]() |
0363c0854f | ||
![]() |
233ddc955b | ||
![]() |
065c711f91 | ||
![]() |
49655c11c9 | ||
![]() |
38fd4eda22 | ||
![]() |
b71630246a | ||
![]() |
fd2eac1f08 | ||
![]() |
1545ff65d3 | ||
![]() |
ff32c82851 | ||
![]() |
d531a94594 | ||
![]() |
71ab9586ac | ||
![]() |
e1a19be06c | ||
![]() |
6f2168d641 | ||
![]() |
cde2121b60 | ||
d7fec4806e | |||
4fba76d327 | |||
![]() |
a0bc37e826 | ||
![]() |
f983a23b1a | ||
![]() |
6a1851da13 | ||
![]() |
ad5381ce34 | ||
![]() |
dbc7587741 | ||
![]() |
bc3aa7b8dc | ||
![]() |
6bf6a9da11 | ||
![]() |
ab175bdd9a | ||
![]() |
8dbbea2138 | ||
![]() |
f6226e6b53 | ||
![]() |
43d13db07c | ||
![]() |
82210c37e3 | ||
![]() |
2816d7217a | ||
![]() |
2fa868173b | ||
![]() |
622c75bb42 | ||
![]() |
2121125283 | ||
![]() |
c72a117e34 | ||
![]() |
b5cc32d59f | ||
![]() |
d943d03266 | ||
![]() |
6eca8c42f5 | ||
![]() |
af989ba9f6 | ||
![]() |
4a65a5b192 | ||
![]() |
bbbafdfe70 | ||
![]() |
860095e862 | ||
![]() |
ff9be43291 | ||
![]() |
a487378daf | ||
![]() |
895f5cbb76 | ||
![]() |
8b9b1460ab | ||
![]() |
7edd3df074 | ||
![]() |
16c51f7b07 | ||
![]() |
7a3a97447f | ||
![]() |
b500d8e204 | ||
![]() |
c34a369286 | ||
![]() |
b9f3ab2e56 | ||
![]() |
6b59973624 | ||
![]() |
d18485293d | ||
![]() |
a82e11d694 | ||
![]() |
4dc5fc65ac | ||
![]() |
7463cf6253 | ||
![]() |
d799ec7ac9 | ||
![]() |
254719f22f | ||
![]() |
596e8df4fc | ||
![]() |
e1e276e1ea | ||
![]() |
8cdd4311a9 | ||
![]() |
b7f7b16aef | ||
![]() |
f13ce6e2b4 | ||
![]() |
8c10606b61 | ||
![]() |
7fda4276d6 | ||
![]() |
7993366bfc | ||
![]() |
2e71c50894 | ||
![]() |
b3faac01a5 | ||
![]() |
3881678208 | ||
![]() |
76d038eefa | ||
![]() |
3a55c3c760 | ||
![]() |
a0818de7d1 | ||
ecae5d96c4 | |||
![]() |
ddd49575d3 | ||
![]() |
5b0748a254 | ||
![]() |
47ff549d4b | ||
![]() |
a1203b360e | ||
![]() |
c80c041dd0 | ||
667b1b14d2 | |||
3f2a02f242 | |||
![]() |
29196c62fa | ||
e5412086d5 | |||
![]() |
ce114e1824 | ||
![]() |
b0d221473c | ||
![]() |
ee2803b0d3 | ||
![]() |
47817c47f8 | ||
![]() |
92bad9d112 | ||
![]() |
b280316b07 | ||
![]() |
0554aa91fd | ||
![]() |
5a77d1e940 | ||
![]() |
d17614fa64 | ||
![]() |
c9a42a6cf6 | ||
![]() |
d068371fb1 | ||
![]() |
af71023bfb | ||
![]() |
b5adc27efa | ||
![]() |
de0b9cf221 | ||
![]() |
425d28d563 | ||
![]() |
31744e7e77 | ||
![]() |
925ad9616d | ||
![]() |
878e7b4247 | ||
![]() |
e347045ac8 | ||
![]() |
8eb65196cb | ||
![]() |
f99df4ad46 | ||
![]() |
27eb0588d7 | ||
![]() |
e17129efea | ||
![]() |
6047af9ff0 | ||
![]() |
d789aa718e | ||
![]() |
8623b53357 | ||
![]() |
78e28ad791 | ||
![]() |
377c288e9e | ||
![]() |
b31c7e1720 | ||
![]() |
d01fe9c370 | ||
![]() |
34d34a050a | ||
![]() |
961bc24f27 | ||
![]() |
8a90b61b97 | ||
![]() |
6a8f6f9496 | ||
![]() |
afb5ae741c | ||
![]() |
95e41b5570 | ||
![]() |
eb6fdd900e | ||
![]() |
88def5eff8 | ||
![]() |
0e99c81eb8 | ||
![]() |
38c00ddab5 | ||
![]() |
c72cc39920 | ||
![]() |
4ef9fb1f28 | ||
![]() |
5dd5697f65 | ||
![]() |
a7c2009e49 | ||
![]() |
a71ef4a4b2 | ||
![]() |
30413086fc | ||
![]() |
98ddf97855 | ||
![]() |
8f5a210ec7 | ||
![]() |
ce09b07cfd | ||
![]() |
5ed19cb21a | ||
![]() |
0a1f7270b4 | ||
![]() |
47d8513a77 | ||
![]() |
00432ab911 | ||
![]() |
7b2c839775 | ||
![]() |
f455064b9d | ||
![]() |
2bbc157d03 | ||
![]() |
a0a0b8dea6 | ||
![]() |
3bab883a56 | ||
![]() |
b319bb03cd | ||
![]() |
333306e7ba | ||
![]() |
fb240938ed | ||
![]() |
dc9af29a44 | ||
![]() |
e9d64de0cb | ||
![]() |
05bda598fc | ||
![]() |
3564366a8f | ||
![]() |
f2d26453ed | ||
![]() |
ccba31f2e8 | ||
![]() |
a7238e3f23 | ||
![]() |
0264682d0d | ||
![]() |
c395abb7e4 | ||
![]() |
dfd5e10fbf | ||
![]() |
130f6164c4 | ||
![]() |
b2191ab21b | ||
![]() |
eb08a2cddb | ||
![]() |
eb63e5eb04 | ||
![]() |
7a8ab6fb52 | ||
![]() |
0eadf36222 | ||
![]() |
85e3bb4d17 | ||
![]() |
84969e6f9b | ||
![]() |
d39998000b | ||
![]() |
b6cbd0523b | ||
![]() |
4d74c252c8 | ||
![]() |
c1687f5856 | ||
![]() |
3eae3a7667 | ||
![]() |
90a5b9e20f | ||
![]() |
b99ba48d2c | ||
![]() |
496695162d | ||
![]() |
1fe464a289 | ||
![]() |
17c139b559 | ||
![]() |
f893170dec | ||
![]() |
f7b25139c0 | ||
![]() |
6e751c83f6 | ||
![]() |
43a01e4e04 | ||
![]() |
ee906d02ae | ||
![]() |
83cf39a28c | ||
![]() |
770749e158 | ||
![]() |
3c5c50f84d | ||
![]() |
d0d3b11662 | ||
![]() |
eb31f9578f | ||
![]() |
d3ff6682ca | ||
![]() |
a2a7d2ebb2 | ||
![]() |
a571dabb71 | ||
![]() |
5c27edbe3c | ||
![]() |
598edaadb7 | ||
![]() |
c64be2fab0 | ||
![]() |
d9bab2af78 | ||
![]() |
aba08e6aa9 | ||
![]() |
387ff1cba7 | ||
![]() |
6071b7571b | ||
![]() |
bcd305bef3 | ||
![]() |
1d8378e136 | ||
![]() |
af346842a3 | ||
![]() |
391f38485d | ||
![]() |
fd482777e8 | ||
![]() |
cc46b3b124 | ||
![]() |
c40cdf88ad | ||
![]() |
5c440010e2 | ||
![]() |
d08f195968 | ||
![]() |
88c38c4a8d | ||
![]() |
9697a39464 | ||
![]() |
18dbbba328 | ||
![]() |
c4672b8de9 |
@ -162,7 +162,7 @@ jobs:
|
||||
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
|
||||
- run:
|
||||
name: Publish release
|
||||
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
|
||||
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
@ -1,7 +1,7 @@
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=true
|
||||
insert_final_newline=Advanced
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
|
||||
|
18
.gitea/release.yml
Normal file
18
.gitea/release.yml
Normal file
@ -0,0 +1,18 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- "release ignore"
|
||||
categories:
|
||||
- title: breaking changes
|
||||
labels:
|
||||
- major
|
||||
- title: new features
|
||||
labels:
|
||||
- minor
|
||||
- fr:approved
|
||||
- title: translation updates
|
||||
labels:
|
||||
- translation
|
||||
- title: features
|
||||
labels:
|
||||
- "*"
|
84
.gitea/workflows/build_android.yml
Normal file
84
.gitea/workflows/build_android.yml
Normal file
@ -0,0 +1,84 @@
|
||||
name: Generate APK
|
||||
|
||||
env:
|
||||
main_project_module: app
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set current date as env variable
|
||||
run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
|
||||
|
||||
- name: Set repository name as env variable
|
||||
run: echo "repository_name=$(echo '${{ gitea.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Set up Go environment
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Get hash of Gradle files
|
||||
uses: https://gitea.com/actions/go-hashfiles@v0.0.1
|
||||
id: get-hash
|
||||
with:
|
||||
patterns: |-
|
||||
**/*.gradle*
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ steps.get-hash.outputs.hash }}
|
||||
|
||||
- name: Get app version
|
||||
id: get_version
|
||||
run: echo "VERSION_NAME=$(grep -m1 "versionName" app/build.gradle | awk '{print $2}' | tr -d \'\'\"\')" >> $GITHUB_ENV
|
||||
|
||||
- name: Change wrapper permissions
|
||||
run: chmod +x ./gradlew
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Build debug APK
|
||||
run: ./gradlew assembleDebug
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3 # not v4 because of GHES
|
||||
with:
|
||||
name: wulkanowy_mod_debug_builds
|
||||
path: |
|
||||
app/build/outputs/**/*-debug.apk
|
||||
|
||||
- name: Create release
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
env:
|
||||
NODE_OPTIONS: '--experimental-fetch'
|
||||
with:
|
||||
files: |
|
||||
app/build/outputs/**/*-debug.apk
|
||||
|
||||
name: Release ${{ env.VERSION_NAME }} (${{ env.date_today }})
|
||||
tag_name: v${{ env.VERSION_NAME }}
|
79
.github/workflows/deploy-store.yml
vendored
79
.github/workflows/deploy-store.yml
vendored
@ -1,79 +0,0 @@
|
||||
name: Deploy release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
|
||||
deploy-google-play:
|
||||
name: Google Play
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
environment: google-play
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
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 }}
|
||||
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
|
||||
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||
|
||||
deploy-app-gallery:
|
||||
name: AppGallery
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
environment: app-gallery
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
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
|
146
.github/workflows/deploy-test.yml
vendored
146
.github/workflows/deploy-test.yml
vendored
@ -1,146 +0,0 @@
|
||||
name: Deploy DEV
|
||||
|
||||
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@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
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@v3
|
||||
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@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
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@v3
|
||||
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
|
90
.github/workflows/test.yml
vendored
90
.github/workflows/test.yml
vendored
@ -1,90 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- 'hotfix/**'
|
||||
tags: [ '*' ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
tests-fdroid:
|
||||
name: F-Droid
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
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@v3
|
||||
with:
|
||||
flags: unit
|
||||
|
||||
tests-play:
|
||||
name: Play
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- name: Unit tests
|
||||
run: |
|
||||
./gradlew testPlayDebugUnitTest --stacktrace
|
||||
./gradlew jacocoTestReport --stacktrace
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
flags: unit
|
||||
|
||||
tests-hms:
|
||||
name: HMS
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- name: Unit tests
|
||||
run: |
|
||||
./gradlew testHmsDebugUnitTest --stacktrace
|
||||
./gradlew jacocoTestReport --stacktrace
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
flags: unit
|
16
.gitignore
vendored
16
.gitignore
vendored
@ -67,6 +67,10 @@ captures/
|
||||
.idea/discord.xml
|
||||
.idea/migrations.xml
|
||||
.idea/androidTestResultsUserPreferences.xml
|
||||
.idea/copilot
|
||||
.idea/deploymentTargetDropDown.xml
|
||||
.idea/deploymentTargetSelector.xml
|
||||
.idea/kotlinc.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
@ -113,12 +117,14 @@ Thumbs.db
|
||||
*.ear
|
||||
|
||||
### AndroidStudio Patch ###
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
.idea/jarRepositories.xml
|
||||
|
||||
### Services config files
|
||||
agconnect-services.json
|
||||
agconnect-credentials.json
|
||||
google-services.json
|
||||
!app/google-services.json
|
||||
|
||||
app/src/release/agconnect-services.json
|
||||
app/src/release/agconnect-credentials.json
|
||||
.idea/deploymentTargetDropDown.xml
|
||||
.idea/kotlinc.xml
|
||||
|
||||
.idea/appInsightsSettings.xml
|
||||
|
@ -61,7 +61,7 @@ script:
|
||||
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;
|
||||
./gradlew publishPlayRelease --stacktrace;
|
||||
fi
|
||||
|
||||
after_success:
|
||||
|
90
README.cs.md
90
README.cs.md
@ -1,73 +1,33 @@
|
||||
Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
# Wulkanowy MOD
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
## Funkce:
|
||||
* skrýt známky
|
||||
* Skrýt jednotlivé záznamy o docházce.
|
||||
* Skrýt komentáře.
|
||||
* falešná docházka %
|
||||
|
||||
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
|
||||
Chcete-li se dostat na skrytý panel:
|
||||
1. Přejděte na kartu „Další“.
|
||||
2. Přejděte na panel „Nastavení“.
|
||||
3. Přejděte na panel „O aplikaci“.
|
||||
4. Klikněte 5x na logo aplikace
|
||||
5. Přejděte na domovskou obrazovku
|
||||
6. Přejděte do nastavení
|
||||
7. Zadejte „tajná nastavení“
|
||||
|
||||
# Instalace
|
||||
|
||||
## Funkce
|
||||
| Název souboru | Přizpůsobeno |
|
||||
| ---------------- | ----------------- |
|
||||
| `*-fdroid-*.apk` | F-Droid |
|
||||
| `*-hms-*.apk` | Huawei AppGallery |
|
||||
| `*-play-*.apk` | Play Store |
|
||||
|
||||
* 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
|
||||
* volitelné reklamy na podporu projektu
|
||||
Stáhněte si vybranou verzi z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
|
||||
Doporučujeme stáhnout nejnovější dostupnou verzi.
|
||||
|
||||
## Stáhnout
|
||||
# O projektu Wulkanowy
|
||||
|
||||
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 pomocí
|
||||
|
||||
|
||||
* [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)
|
||||
Chcete si přečíst více o projektu Wulkanowy? [Klikněte sem](https://github.com/wulkanowy/wulkanowy)
|
90
README.de.md
90
README.de.md
@ -1,73 +1,33 @@
|
||||
[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
# Wulkanowy MOD
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
## Funktionen:
|
||||
* Noten ausblenden
|
||||
* Individuelle Anwesenheitslisten ausblenden.
|
||||
* Kommentare ausblenden.
|
||||
* Anwesenheit fälschen %
|
||||
|
||||
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
|
||||
So gelangen Sie zum ausgeblendeten Bereich:
|
||||
1. Gehen Sie zur Registerkarte „Mehr“.
|
||||
2. Gehen Sie zum Bereich „Einstellungen“.
|
||||
3. Gehen Sie zum Bereich „Über die Anwendung“.
|
||||
4. Klicken Sie fünfmal auf das Anwendungslogo
|
||||
5. Gehen Sie zum Startbildschirm
|
||||
6. Gehen Sie zu den Einstellungen
|
||||
7. Geben Sie „Geheime Einstellungen“ ein
|
||||
|
||||
# Installation
|
||||
|
||||
## Merkmale
|
||||
| Dateiname | Angepasst an |
|
||||
| ---------------- | ----------------- |
|
||||
| `*-fdroid-*.apk` | F-Droid |
|
||||
| `*-hms-*.apk` | Huawei AppGallery |
|
||||
| `*-play-*.apk` | Play Store |
|
||||
|
||||
* Einloggen mit E-Mail und Passwort
|
||||
* Funktionen von der Registerwebsite:
|
||||
* Noten
|
||||
* Notenstatistik
|
||||
* Anwesenheit
|
||||
* Prozentsatz der Anwesenheit
|
||||
* Prüfungen
|
||||
* Stundenplan
|
||||
* abgeschlossene Unterrichtsstunden
|
||||
* 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
|
||||
* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen
|
||||
Laden Sie die ausgewählte Version von [hier](https://git.sador.me/sadorowo/wulkanowy-mod/releases) herunter.
|
||||
Wir empfehlen, die neueste verfügbare Version herunterzuladen.
|
||||
|
||||
## Herunterladen
|
||||
# Über das Wulkanowy-Projekt
|
||||
|
||||
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 eine [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) die 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
|
||||
Möchten Sie mehr über das Wulkanowy-Projekt lesen? [Hier klicken](https://github.com/wulkanowy/wulkanowy)
|
90
README.en.md
90
README.en.md
@ -1,73 +1,33 @@
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
# Wulkanowy MOD
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
## Functions:
|
||||
* hide grades
|
||||
* hide individual attendance entries
|
||||
* hide comments
|
||||
* fake attendance %.
|
||||
|
||||
Unofficial android VULCAN UONET+ register client for both students and their parents
|
||||
To get to the hidden panel:
|
||||
1. Go to the "More" tab
|
||||
2. Go to the "Settings" panel
|
||||
3. Go to the "About application" panel
|
||||
4. Click on the application logo 5 times
|
||||
5. Go to the home screen
|
||||
6. Go to settings
|
||||
7. Enter "secret settings"
|
||||
|
||||
# Installation
|
||||
|
||||
## Features
|
||||
| File name | Adapted to |
|
||||
| ---------------- | ----------------- |
|
||||
| `*-fdroid-*.apk` | F-Droid |
|
||||
| `*-hms-*.apk` | Huawei AppGallery |
|
||||
| `*-play-*.apk` | Play Store |
|
||||
|
||||
* logging in using the email and password
|
||||
* functions from the register website:
|
||||
* grades
|
||||
* grade statistics
|
||||
* attendance
|
||||
* percentage of attendance
|
||||
* exams
|
||||
* timetable
|
||||
* completed lessons
|
||||
* messages
|
||||
* 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
|
||||
* optional ads which allow to support the project
|
||||
Download application from [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
|
||||
We recommend downloading the latest available version.
|
||||
|
||||
## Download
|
||||
# About the Wulkanowy project
|
||||
|
||||
You can download the current 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"
|
||||
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=)
|
||||
|
||||
You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release
|
||||
|
||||
## Built With
|
||||
|
||||
|
||||
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||
* [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)
|
||||
|
||||
## Contributing
|
||||
|
||||
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
|
||||
Want to read more about the Wulkanowy project? [Click here](https://github.com/wulkanowy/wulkanowy)
|
91
README.md
91
README.md
@ -1,74 +1,33 @@
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
# Wulkanowy MOD
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
## Funkcje:
|
||||
* ukryj oceny
|
||||
* ukryj poszczególne wpisy frekwencji
|
||||
* ukryj uwagi
|
||||
* sfałszuj % frekwencji
|
||||
|
||||
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
Aby dostać się do ukrytego panelu:
|
||||
1. Przejdź do karty "Więcej"
|
||||
2. Przejdź do panelu "Ustawienia"
|
||||
3. Przejdź do panelu "O aplikacji"
|
||||
4. Kliknij 5 razy w logo aplikacji
|
||||
5. Przejdź na ekran główny
|
||||
6. Wejdź w ustawienia
|
||||
7. Wejdź w "sekretne ustawienia"
|
||||
|
||||
# Instalacja
|
||||
|
||||
## Funkcje
|
||||
| Nazwa pliku | Przystosowana do |
|
||||
| ---------------- | ----------------- |
|
||||
| `*-fdroid-*.apk` | F-Droid |
|
||||
| `*-hms-*.apk` | Huawei AppGallery |
|
||||
| `*-play-*.apk` | Sklep Play |
|
||||
|
||||
* logowanie za pomocą e-maila i hasła
|
||||
* funkcje ze strony internetowej dziennika:
|
||||
* oceny
|
||||
* statystyki ocen
|
||||
* frekwencja
|
||||
* procent frekwencji
|
||||
* sprawdziany
|
||||
* plan lekcji
|
||||
* lekcje zrealizowane
|
||||
* wiadomości
|
||||
* 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
|
||||
* opcjonalne reklamy umożliwiające wsparcie projektu
|
||||
Pobierz wybraną wersję z [wydań](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
|
||||
Zalecamy pobranie najnowszej dostępnej wersji.
|
||||
|
||||
## Pobierz
|
||||
# O projekcie Wulkanowy
|
||||
|
||||
Aktualną wersję 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"
|
||||
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="Pobierz z F-Droid"
|
||||
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[<img src="appgallery_badge.png"
|
||||
alt="Odkrywaj w AppGallery"
|
||||
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
|
||||
|
||||
|
||||
Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania
|
||||
|
||||
|
||||
## Zbudowana za pomocą
|
||||
|
||||
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||
* [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)
|
||||
|
||||
## Współpraca
|
||||
|
||||
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)
|
||||
Chcesz poczytać więcej o projekcie Wulkanowy? [Kliknij tutaj](https://github.com/wulkanowy/wulkanowy)
|
90
README.sk.md
90
README.sk.md
@ -1,73 +1,33 @@
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia
|
||||
|
||||
# Wulkanowy
|
||||
# Wulkanowy MOD
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
## Funkcie:
|
||||
* skryť známky
|
||||
* Skryť individuálne záznamy o dochádzke.
|
||||
* Skryť komentáre.
|
||||
* falošná dochádzka %
|
||||
|
||||
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
|
||||
Ak chcete prejsť na skrytý panel:
|
||||
1. Prejdite na kartu „Viac“.
|
||||
2. Prejdite na panel „Nastavenia“.
|
||||
3. Prejdite na panel „O aplikácii“.
|
||||
4. Kliknite 5-krát na logo aplikácie
|
||||
5. Prejdite na domovskú obrazovku
|
||||
6. Prejdite do nastavení
|
||||
7. Zadajte „tajné nastavenia“
|
||||
|
||||
# Inštalácia
|
||||
|
||||
## Funkcie
|
||||
| Názov súboru | Prispôsobené |
|
||||
| ---------------- | ----------------- |
|
||||
| `*-fdroid-*.apk` | F-Droid |
|
||||
| `*-hms-*.apk` | Huawei AppGallery |
|
||||
| `*-play-*.apk` | Play Store |
|
||||
|
||||
* 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
|
||||
* voliteľné reklamy na podporu projektu
|
||||
Stiahnite si vybranú verziu z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
|
||||
Odporúčame stiahnuť najnovšiu dostupnú verziu.
|
||||
|
||||
## Stiahnuť
|
||||
# O projekte Wulkanowy
|
||||
|
||||
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 pomocou
|
||||
|
||||
|
||||
* [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)
|
||||
Chcete si prečítať viac o projekte Wulkanowy? [Kliknite sem](https://github.com/wulkanowy/wulkanowy)
|
@ -27,15 +27,12 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 148
|
||||
versionName "2.4.2"
|
||||
versionCode 173
|
||||
versionName "2.6.13"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||
admob_project_id: ""
|
||||
]
|
||||
manifestPlaceholders = [admob_project_id: ""]
|
||||
|
||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
||||
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
|
||||
@ -65,8 +62,8 @@ android {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
// signingConfig signingConfigs.release
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
||||
}
|
||||
@ -76,7 +73,6 @@ android {
|
||||
resValue "string", "app_name", "Wulkanowy DEV"
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
||||
}
|
||||
@ -164,7 +160,7 @@ play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.99d
|
||||
userFraction = 0.1d
|
||||
updatePriority = 2
|
||||
enabled.set(false)
|
||||
}
|
||||
@ -190,28 +186,30 @@ ext {
|
||||
android_hilt = "1.2.0"
|
||||
room = "2.6.1"
|
||||
chucker = "4.0.0"
|
||||
mockk = "1.13.9"
|
||||
coroutines = "1.8.0"
|
||||
mockk = "1.13.10"
|
||||
coroutines = "1.8.1"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
|
||||
implementation 'io.github.wulkanowy:sdk:2.6.11'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines"
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.activity:activity-ktx:1.8.2"
|
||||
implementation "androidx.activity:activity-ktx:1.9.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.6.2"
|
||||
implementation "androidx.fragment:fragment-ktx:1.7.0"
|
||||
implementation "androidx.annotation:annotation:1.7.1"
|
||||
implementation "androidx.javascriptengine:javascriptengine:1.0.0-beta01"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-beta02"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-rc01"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
@ -237,7 +235,7 @@ dependencies {
|
||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
|
||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||
@ -250,9 +248,9 @@ dependencies {
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:33.0.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics'
|
||||
playImplementation 'com.google.firebase:firebase-messaging'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
@ -278,7 +276,7 @@ dependencies {
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.11.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.12.1'
|
||||
testImplementation "androidx.test:runner:1.5.2"
|
||||
testImplementation "androidx.test.ext:junit:1.1.5"
|
||||
testImplementation "androidx.test:core:1.5.0"
|
||||
|
@ -36,6 +36,37 @@
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
|
||||
"android_client_info": {
|
||||
"package_name": "io.github.wulkanowy"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": ""
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
@ -1,7 +1,8 @@
|
||||
#!/bin/bash -
|
||||
|
||||
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
|
||||
if [[ "${#content}" -gt 500 ]]; then
|
||||
content2=echo "$content" | dos2unix
|
||||
if [[ "${#content2}" -gt 500 ]]; then
|
||||
echo >&2 "Release notes content has reached the limit of 500 characters"
|
||||
exit 1
|
||||
fi
|
||||
|
2533
app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
Normal file
2533
app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
Normal file
File diff suppressed because it is too large
Load Diff
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json
Normal file
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json
Normal file
File diff suppressed because it is too large
Load Diff
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
Normal file
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
Normal file
File diff suppressed because it is too large
Load Diff
2559
app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
Normal file
2559
app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,92 +0,0 @@
|
||||
{
|
||||
"agcgw": {
|
||||
"backurl": "connect-dre.hispace.hicloud.com",
|
||||
"url": "connect-dre.dbankcloud.cn",
|
||||
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
|
||||
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
|
||||
},
|
||||
"agcgw_all": {
|
||||
"CN": "connect-drcn.dbankcloud.cn",
|
||||
"CN_back": "connect-drcn.hispace.hicloud.com",
|
||||
"DE": "connect-dre.dbankcloud.cn",
|
||||
"DE_back": "connect-dre.hispace.hicloud.com",
|
||||
"RU": "connect-drru.hispace.dbankcloud.ru",
|
||||
"RU_back": "connect-drru.hispace.dbankcloud.cn",
|
||||
"SG": "connect-dra.dbankcloud.cn",
|
||||
"SG_back": "connect-dra.hispace.hicloud.com"
|
||||
},
|
||||
"websocketgw_all": {
|
||||
"CN": "connect-ws-drcn.hispace.dbankcloud.cn",
|
||||
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
|
||||
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
|
||||
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
|
||||
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
|
||||
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
|
||||
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
|
||||
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
|
||||
},
|
||||
"client": {
|
||||
"cp_id": "890048000024105546",
|
||||
"product_id": "736430079244736562",
|
||||
"client_id": "514530959291319360",
|
||||
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
|
||||
"project_id": "736430079244736562",
|
||||
"app_id": "106552551",
|
||||
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
|
||||
"package_name": "io.github.wulkanowy.dev"
|
||||
},
|
||||
"oauth_client": {
|
||||
"client_id": "106552551",
|
||||
"client_type": 1
|
||||
},
|
||||
"app_info": {
|
||||
"app_id": "106552551",
|
||||
"package_name": "io.github.wulkanowy.dev"
|
||||
},
|
||||
"service": {
|
||||
"analytics": {
|
||||
"collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
|
||||
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
|
||||
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
|
||||
"resource_id": "p1",
|
||||
"channel_id": ""
|
||||
},
|
||||
"search":{
|
||||
"url":"https://search-dre.cloud.huawei.com"
|
||||
},
|
||||
"cloudstorage": {
|
||||
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
|
||||
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
|
||||
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
|
||||
"storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu",
|
||||
"storage_url_de": "https://ops-dre.agcstorage.link",
|
||||
"storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn",
|
||||
"storage_url_sg": "https://ops-dra.agcstorage.link",
|
||||
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
|
||||
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
|
||||
},
|
||||
"ml": {
|
||||
"mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
|
||||
}
|
||||
},
|
||||
"region": "DE",
|
||||
"configuration_version": "3.0",
|
||||
"appInfos": [
|
||||
{
|
||||
"package_name": "io.github.wulkanowy.dev",
|
||||
"client": {
|
||||
"app_id": "106552551"
|
||||
},
|
||||
"app_info": {
|
||||
"package_name": "io.github.wulkanowy.dev",
|
||||
"app_id": "106552551"
|
||||
},
|
||||
"oauth_client": {
|
||||
"client_type": 1,
|
||||
"client_id": "106552551"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="androidx.javascriptengine" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
@ -42,16 +44,16 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
android:resizeableActivity="true"
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
tools:ignore="DiscouragedApi,LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ -155,33 +157,9 @@
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
android:enabled="${firebase_enabled}"
|
||||
android:exported="false"
|
||||
tools:ignore="MissingClass" />
|
||||
|
||||
<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" />
|
||||
|
@ -13,22 +13,18 @@ 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.api.SchoolsService
|
||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
||||
import io.github.wulkanowy.data.api.services.WulkanowyService
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||
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
|
||||
|
||||
@ -36,20 +32,6 @@ import javax.inject.Singleton
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
|
||||
Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChuckerCollector(
|
||||
@ -89,7 +71,7 @@ internal class DataModule {
|
||||
okHttpClient: OkHttpClient,
|
||||
json: Json,
|
||||
appInfo: AppInfo
|
||||
): AdminMessageService = Retrofit.Builder()
|
||||
): WulkanowyService = Retrofit.Builder()
|
||||
.baseUrl(appInfo.messagesBaseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
|
@ -1,11 +1,17 @@
|
||||
package io.github.wulkanowy.data
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
@ -14,16 +20,39 @@ import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
sealed class Resource<T> {
|
||||
|
||||
open class Loading<T> : Resource<T>()
|
||||
sealed interface Resource<out T> {
|
||||
/**
|
||||
* The initial value of a resource flow. Indicates no data that is currently available to be shown,
|
||||
* however with the expectation that the state will transition to another one soon.
|
||||
*/
|
||||
open class Loading<T> : Resource<T>
|
||||
|
||||
/**
|
||||
* A semi-loading state with some data available to be displayed (usually cached data loaded from
|
||||
* the database). Still not the target state and it's expected to transition into another one soon.
|
||||
*/
|
||||
data class Intermediate<T>(val data: T) : Loading<T>()
|
||||
|
||||
data class Success<T>(val data: T) : Resource<T>()
|
||||
/**
|
||||
* The happy-path target state. Data can either be:
|
||||
* - loaded from the database - while it may seem like this case is already handled by the
|
||||
* Intermediate state, the difference here is semantic. Cached data is returned as Intermediate
|
||||
* when there's a API request in progress (or soon expected to be), however when there is no
|
||||
* intention of immediately querying the API, the cached data is returned as a Success.
|
||||
* - fetched from the API.
|
||||
*/
|
||||
data class Success<T>(val data: T) : Resource<T>
|
||||
|
||||
data class Error<T>(val error: Throwable) : Resource<T>()
|
||||
/**
|
||||
* Something bad happened and we were unable to get the requested data. This can be caused by
|
||||
* a database error, a network error, or really just any other error. Upon receiving this state
|
||||
* the UI can either: display a full screen error, or, when it has received any data previously,
|
||||
* display a snack bar informing of the problem.
|
||||
*/
|
||||
data class Error<T>(val error: Throwable) : Resource<T>
|
||||
}
|
||||
|
||||
val <T> Resource<T>.dataOrNull: T?
|
||||
@ -64,6 +93,22 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
|
||||
is Resource.Error -> Resource.Error(this.error)
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects another flow into this flow's resource data.
|
||||
*/
|
||||
inline fun <T1, T2, R> Flow<Resource<T1>>.combineWithResourceData(
|
||||
flow: Flow<T2>,
|
||||
crossinline block: suspend (T1, T2) -> R
|
||||
): Flow<Resource<R>> =
|
||||
combine(flow) { resource, inject ->
|
||||
when (resource) {
|
||||
is Resource.Success -> Resource.Success(block(resource.data, inject))
|
||||
is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject))
|
||||
is Resource.Loading -> Resource.Loading()
|
||||
is Resource.Error -> Resource.Error(resource.error)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
|
||||
val description = when (it) {
|
||||
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
|
||||
@ -74,8 +119,29 @@ fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = fa
|
||||
Timber.i("$name: $description")
|
||||
}
|
||||
|
||||
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
|
||||
it.mapData(block)
|
||||
inline fun <T, U> Flow<Resource<T>>.mapResourceData(crossinline block: suspend (T) -> U) = map {
|
||||
when (it) {
|
||||
is Resource.Success -> Resource.Success(block(it.data))
|
||||
is Resource.Intermediate -> Resource.Intermediate(block(it.data))
|
||||
is Resource.Loading -> Resource.Loading()
|
||||
is Resource.Error -> Resource.Error(it.error)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun <T, U> Flow<Resource<T>>.flatMapResourceData(
|
||||
inheritIntermediate: Boolean = true, block: suspend (T) -> Flow<Resource<U>>
|
||||
) = flatMapLatest {
|
||||
when (it) {
|
||||
is Resource.Success -> block(it.data)
|
||||
is Resource.Intermediate -> block(it.data).map { newRes ->
|
||||
if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data)
|
||||
else newRes
|
||||
}
|
||||
|
||||
is Resource.Loading -> flowOf(Resource.Loading())
|
||||
is Resource.Error -> flowOf(Resource.Error(it.error))
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
|
||||
@ -105,13 +171,13 @@ fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
|
||||
fun <T> Flow<Resource<T>>.onResourceError(block: suspend (Throwable) -> Unit) = onEach {
|
||||
if (it is Resource.Error) {
|
||||
block(it.error)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
|
||||
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: suspend () -> Unit) = onEach {
|
||||
if (it !is Resource.Loading) {
|
||||
block()
|
||||
}
|
||||
@ -121,70 +187,99 @@ suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loa
|
||||
|
||||
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
|
||||
|
||||
inline fun <ResultType, RequestType> networkBoundResource(
|
||||
mutex: Mutex = Mutex(),
|
||||
showSavedOnLoading: Boolean = true,
|
||||
crossinline isResultEmpty: (ResultType) -> Boolean,
|
||||
crossinline query: () -> Flow<ResultType>,
|
||||
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||
crossinline filterResult: (ResultType) -> ResultType = { it }
|
||||
) = flow {
|
||||
emit(Resource.Loading())
|
||||
// Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired,
|
||||
// use `debounceIntermediates` to alleviate this behavior.
|
||||
inline fun <reified T> combineResourceFlows(flows: Iterable<Flow<Resource<T>>>): Flow<Resource<List<T>>> =
|
||||
combine(flows) { items ->
|
||||
var isIntermediate = false
|
||||
val data = mutableListOf<T>()
|
||||
for (item in items) {
|
||||
when (item) {
|
||||
is Resource.Success -> data.add(item.data)
|
||||
is Resource.Intermediate -> {
|
||||
isIntermediate = true
|
||||
data.add(item.data)
|
||||
}
|
||||
|
||||
val data = query().first()
|
||||
emitAll(if (shouldFetch(data)) {
|
||||
val filteredResult = filterResult(data)
|
||||
|
||||
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
|
||||
emit(Resource.Intermediate(filteredResult))
|
||||
is Resource.Loading -> return@combine Resource.Loading()
|
||||
is Resource.Error -> continue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val newData = fetch(data)
|
||||
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||
query().map { Resource.Success(filterResult(it)) }
|
||||
} catch (throwable: Throwable) {
|
||||
onFetchFailed(throwable)
|
||||
flowOf(Resource.Error(throwable))
|
||||
if (data.isEmpty()) {
|
||||
// All items have to be errors for this to happen, so just return the first one.
|
||||
// mapData is functionally useless and exists only to satisfy the type checker
|
||||
items.first().mapData { listOf(it) }
|
||||
} else if (isIntermediate) {
|
||||
Resource.Intermediate(data)
|
||||
} else {
|
||||
Resource.Success(data)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
fun <T> Flow<Resource<T>>.debounceIntermediates(timeout: Duration = 5.seconds) = flow {
|
||||
var wasIntermediate = false
|
||||
|
||||
emitAll(this@debounceIntermediates.debounce {
|
||||
if (it is Resource.Intermediate) {
|
||||
if (!wasIntermediate) {
|
||||
wasIntermediate = true
|
||||
Duration.ZERO
|
||||
} else {
|
||||
timeout
|
||||
}
|
||||
} else {
|
||||
wasIntermediate = false
|
||||
Duration.ZERO
|
||||
}
|
||||
} else {
|
||||
query().map { Resource.Success(filterResult(it)) }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
inline fun <OutputType, ApiType> networkBoundResource(
|
||||
mutex: Mutex = Mutex(),
|
||||
crossinline isResultEmpty: (OutputType) -> Boolean,
|
||||
crossinline query: () -> Flow<OutputType>,
|
||||
crossinline fetch: suspend () -> ApiType,
|
||||
crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit,
|
||||
crossinline shouldFetch: (OutputType) -> Boolean = { true },
|
||||
crossinline filterResult: (OutputType) -> OutputType = { it }
|
||||
) = networkBoundResource(
|
||||
mutex = mutex,
|
||||
isResultEmpty = isResultEmpty,
|
||||
query = query,
|
||||
fetch = fetch,
|
||||
saveFetchResult = saveFetchResult,
|
||||
shouldFetch = shouldFetch,
|
||||
mapResult = filterResult
|
||||
)
|
||||
|
||||
@JvmName("networkBoundResourceWithMap")
|
||||
inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||
inline fun <DatabaseType, ApiType, OutputType> networkBoundResource(
|
||||
mutex: Mutex = Mutex(),
|
||||
showSavedOnLoading: Boolean = true,
|
||||
crossinline isResultEmpty: (T) -> Boolean,
|
||||
crossinline query: () -> Flow<ResultType>,
|
||||
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||
crossinline mapResult: (ResultType) -> T,
|
||||
crossinline isResultEmpty: (OutputType) -> Boolean,
|
||||
crossinline query: () -> Flow<DatabaseType>,
|
||||
crossinline fetch: suspend () -> ApiType,
|
||||
crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit,
|
||||
crossinline shouldFetch: (DatabaseType) -> Boolean = { true },
|
||||
crossinline mapResult: (DatabaseType) -> OutputType,
|
||||
) = flow {
|
||||
emit(Resource.Loading())
|
||||
|
||||
val data = query().first()
|
||||
emitAll(if (shouldFetch(data)) {
|
||||
val mappedResult = mapResult(data)
|
||||
if (shouldFetch(data)) {
|
||||
emit(Resource.Intermediate(data))
|
||||
|
||||
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
|
||||
emit(Resource.Intermediate(mappedResult))
|
||||
}
|
||||
try {
|
||||
val newData = fetch(data)
|
||||
val newData = fetch()
|
||||
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||
query().map { Resource.Success(mapResult(it)) }
|
||||
} catch (throwable: Throwable) {
|
||||
onFetchFailed(throwable)
|
||||
flowOf(Resource.Error(throwable))
|
||||
emit(Resource.Error(throwable))
|
||||
return@flow
|
||||
}
|
||||
} else {
|
||||
query().map { Resource.Success(mapResult(it)) }
|
||||
})
|
||||
}
|
||||
|
||||
emitAll(query().map { Resource.Success(it) })
|
||||
}
|
||||
.mapResourceData { mapResult(it) }
|
||||
.filterNot { it is Resource.Intermediate && isResultEmpty(it.data) }
|
||||
|
@ -0,0 +1,171 @@
|
||||
package io.github.wulkanowy.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.javascriptengine.JavaScriptSandbox
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.scrapper.EvaluateHandler
|
||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class WulkanowySdkFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val chuckerInterceptor: ChuckerInterceptor,
|
||||
private val remoteConfig: RemoteConfigHelper,
|
||||
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
||||
private val studentDb: StudentDao,
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
) {
|
||||
|
||||
private val eduOneMutex = Mutex()
|
||||
private val migrationFailedStudentIds = mutableSetOf<Long>()
|
||||
private val sandbox: ListenableFuture<JavaScriptSandbox>? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && JavaScriptSandbox.isSupported())
|
||||
JavaScriptSandbox.createConnectedInstanceAsync(context)
|
||||
else null
|
||||
|
||||
private val sdk = Sdk().apply {
|
||||
androidVersion = Build.VERSION.RELEASE
|
||||
buildTag = Build.MODEL
|
||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
setAdditionalCookieManager(webkitCookieManagerProxy)
|
||||
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
|
||||
fun createBase() = sdk
|
||||
|
||||
suspend fun create(): Sdk {
|
||||
val mapping = wulkanowyRepository.getMapping()
|
||||
|
||||
return createBase().apply {
|
||||
if (mapping != null) {
|
||||
endpointsMapping = mapping.endpoints
|
||||
vTokenMapping = mapping.vTokens
|
||||
vHeaders = mapping.vHeaders
|
||||
vParamsEvaluation = createIsolate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createIsolate(): suspend () -> EvaluateHandler {
|
||||
return {
|
||||
val isolate = sandbox?.await()?.createIsolate()
|
||||
object : EvaluateHandler {
|
||||
override suspend fun evaluate(code: String): String? {
|
||||
return isolate?.evaluateJavaScriptAsync(code)?.await()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
isolate?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun create(student: Student, semester: Semester? = null): Sdk {
|
||||
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
||||
return buildSdk(student, semester, overrideIsEduOne)
|
||||
}
|
||||
|
||||
private suspend fun buildSdk(
|
||||
student: Student,
|
||||
semester: Semester?,
|
||||
isStudentEduOne: Boolean
|
||||
): Sdk {
|
||||
return create().apply {
|
||||
email = student.email
|
||||
password = student.password
|
||||
symbol = student.symbol
|
||||
schoolSymbol = student.schoolSymbol
|
||||
studentId = student.studentId
|
||||
classId = student.classId
|
||||
emptyCookieJarInterceptor = true
|
||||
isEduOne = isStudentEduOne
|
||||
|
||||
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
||||
mobileBaseUrl = student.mobileBaseUrl
|
||||
} else {
|
||||
scrapperBaseUrl = student.scrapperBaseUrl
|
||||
domainSuffix = student.scrapperDomainSuffix
|
||||
loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
|
||||
}
|
||||
|
||||
mode = Sdk.Mode.valueOf(student.loginMode)
|
||||
mobileBaseUrl = student.mobileBaseUrl
|
||||
keyId = student.certificateKey
|
||||
privatePem = student.privateKey
|
||||
|
||||
if (semester != null) {
|
||||
diaryId = semester.diaryId
|
||||
kindergartenDiaryId = semester.kindergartenDiaryId
|
||||
schoolYear = semester.schoolYear
|
||||
unitId = semester.unitId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
|
||||
if (student.isEduOne != null) return student.isEduOne
|
||||
|
||||
if (student.id in migrationFailedStudentIds) {
|
||||
Timber.i("Migration eduOne: skipping because of previous failure")
|
||||
return false
|
||||
}
|
||||
|
||||
eduOneMutex.withLock {
|
||||
if (student.id in migrationFailedStudentIds) {
|
||||
Timber.i("Migration eduOne: skipping because of previous failure")
|
||||
return false
|
||||
}
|
||||
|
||||
val studentFromDatabase = studentDb.loadById(student.id)
|
||||
if (studentFromDatabase?.isEduOne != null) {
|
||||
Timber.i("Migration eduOne: already done")
|
||||
return studentFromDatabase.isEduOne
|
||||
}
|
||||
|
||||
Timber.i("Migration eduOne: flag missing. Running migration...")
|
||||
val initializedSdk = buildSdk(
|
||||
student = student,
|
||||
semester = null,
|
||||
isStudentEduOne = false, // doesn't matter
|
||||
)
|
||||
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
||||
.onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
|
||||
.getOrNull()
|
||||
|
||||
if (newCurrentStudent == null) {
|
||||
Timber.i("Migration eduOne: failed, so skipping")
|
||||
migrationFailedStudentIds.add(student.id)
|
||||
return false
|
||||
}
|
||||
|
||||
Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
|
||||
|
||||
val studentIsEduOne = StudentIsEduOne(
|
||||
id = student.id,
|
||||
isEduOne = newCurrentStudent.isEduOne
|
||||
)
|
||||
studentDb.update(studentIsEduOne)
|
||||
return newCurrentStudent.isEduOne
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.github.wulkanowy.data.api.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Mapping(
|
||||
|
||||
@SerialName("endpoints")
|
||||
val endpoints: Map<String, Map<String, Map<String, String>>>,
|
||||
|
||||
@SerialName("vTokens")
|
||||
val vTokens: Map<String, Map<String, Map<String, String>>>,
|
||||
|
||||
@SerialName("vTokenScheme")
|
||||
val vTokenScheme: Map<String, Map<String, String>> = emptyMap(),
|
||||
|
||||
@SerialName("vHeaders")
|
||||
val vHeaders: Map<String, Map<String, Map<String, String>>> = emptyMap(),
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package io.github.wulkanowy.data.api
|
||||
package io.github.wulkanowy.data.api.services
|
||||
|
||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
@ -1,12 +1,16 @@
|
||||
package io.github.wulkanowy.data.api
|
||||
package io.github.wulkanowy.data.api.services
|
||||
|
||||
import io.github.wulkanowy.data.api.models.Mapping
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import retrofit2.http.GET
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
interface AdminMessageService {
|
||||
interface WulkanowyService {
|
||||
|
||||
@GET("/v1.json")
|
||||
suspend fun getAdminMessages(): List<AdminMessage>
|
||||
}
|
||||
|
||||
@GET("/mapping2.json")
|
||||
suspend fun getMapping(): Mapping
|
||||
}
|
@ -120,6 +120,7 @@ import io.github.wulkanowy.data.db.migrations.Migration55
|
||||
import io.github.wulkanowy.data.db.migrations.Migration57
|
||||
import io.github.wulkanowy.data.db.migrations.Migration58
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration63
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||
@ -173,6 +174,10 @@ import javax.inject.Singleton
|
||||
AutoMigration(from = 57, to = 58, spec = Migration58::class),
|
||||
AutoMigration(from = 58, to = 59),
|
||||
AutoMigration(from = 59, to = 60),
|
||||
AutoMigration(from = 60, to = 61),
|
||||
AutoMigration(from = 61, to = 62),
|
||||
AutoMigration(from = 62, to = 63, spec = Migration63::class),
|
||||
AutoMigration(from = 63, to = 64),
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -181,7 +186,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 60
|
||||
const val VERSION_SCHEMA = 64
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -308,6 +313,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val adminMessagesDao: AdminMessageDao
|
||||
|
||||
abstract val mutedMessageSendersDao: MutedMessageSendersDao
|
||||
|
||||
|
||||
abstract val gradeDescriptiveDao: GradeDescriptiveDao
|
||||
}
|
||||
|
@ -2,24 +2,14 @@ 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> {
|
||||
interface 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)
|
||||
}
|
||||
fun loadAll(): Flow<List<AdminMessage>>
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Singleton
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
|
||||
interface BaseDao<T> {
|
||||
@ -15,4 +16,10 @@ interface BaseDao<T> {
|
||||
|
||||
@Delete
|
||||
suspend fun deleteAll(items: List<T>)
|
||||
|
||||
@Transaction
|
||||
suspend fun removeOldAndSaveNew(oldItems: List<T>, newItems: List<T>) {
|
||||
deleteAll(oldItems)
|
||||
insertAll(newItems)
|
||||
}
|
||||
}
|
||||
|
@ -12,4 +12,8 @@ interface GradeDao : BaseDao<Grade> {
|
||||
|
||||
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
|
||||
fun loadAll(semesterId: Int, studentId: Int): Flow<List<Grade>>
|
||||
|
||||
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId " +
|
||||
"AND entry NOT IN(:censoredEntries)")
|
||||
fun loadAllCensored(semesterId: Int, studentId: Int, censoredEntries: Array<String>): Flow<List<Grade>>
|
||||
}
|
||||
|
@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
@Dao
|
||||
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
||||
|
||||
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
|
||||
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC")
|
||||
fun loadAll(studentId: Int): Flow<List<MobileDevice>>
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
@Dao
|
||||
interface NoteDao : BaseDao<Note> {
|
||||
|
||||
@Query("SELECT * FROM Notes WHERE student_id = :studentId")
|
||||
fun loadAll(studentId: Int): Flow<List<Note>>
|
||||
}
|
||||
|
@ -10,6 +10,6 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
||||
|
||||
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
|
||||
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC")
|
||||
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
|
||||
}
|
||||
|
@ -14,6 +14,6 @@ interface SemesterDao : BaseDao<Semester> {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertSemesters(items: List<Semester>): List<Long>
|
||||
|
||||
@Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
|
||||
@Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)")
|
||||
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
|
||||
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
||||
import io.github.wulkanowy.data.db.entities.StudentName
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import javax.inject.Singleton
|
||||
@ -23,6 +25,12 @@ abstract class StudentDao {
|
||||
@Delete
|
||||
abstract suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
abstract suspend fun update(studentIsEduOne: StudentIsEduOne)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@ -39,11 +47,11 @@ abstract class StudentDao {
|
||||
abstract suspend fun loadAll(): List<Student>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id")
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)")
|
||||
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id")
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
|
||||
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
||||
|
||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||
|
@ -4,6 +4,8 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ -34,6 +36,8 @@ data class AdminMessage(
|
||||
|
||||
val priority: String,
|
||||
|
||||
@SerialName("messageTypes")
|
||||
@Serializable(with = SafeMessageTypeEnumListSerializer::class)
|
||||
@ColumnInfo(name = "types", defaultValue = "[]")
|
||||
val types: List<MessageType> = emptyList(),
|
||||
|
||||
|
@ -33,7 +33,13 @@ data class GradeSummary(
|
||||
@ColumnInfo(name = "points_sum")
|
||||
val pointsSum: String,
|
||||
|
||||
val average: Double
|
||||
@ColumnInfo(name = "points_sum_all_year")
|
||||
val pointsSumAllYear: String?,
|
||||
|
||||
val average: Double,
|
||||
|
||||
@ColumnInfo(name = "average_all_year")
|
||||
val averageAllYear: Double? = null,
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
@ -9,8 +9,8 @@ import java.time.Instant
|
||||
@Entity(tableName = "MobileDevices")
|
||||
data class MobileDevice(
|
||||
|
||||
@ColumnInfo(name = "user_login_id")
|
||||
val userLoginId: Int,
|
||||
@ColumnInfo(name = "user_login_id") // todo: change column name
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "device_id")
|
||||
val deviceId: Int,
|
||||
|
@ -9,14 +9,16 @@ import java.time.LocalDate
|
||||
@Entity(tableName = "SchoolAnnouncements")
|
||||
data class SchoolAnnouncement(
|
||||
|
||||
@ColumnInfo(name = "user_login_id")
|
||||
val userLoginId: Int,
|
||||
@ColumnInfo(name = "user_login_id") // todo: change column name
|
||||
val studentId: Int,
|
||||
|
||||
val date: LocalDate,
|
||||
|
||||
val subject: String,
|
||||
|
||||
val content: String
|
||||
val content: String,
|
||||
|
||||
val author: String? = null,
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
|
@ -49,6 +49,7 @@ data class Student(
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@Deprecated("not available in VULCAN anymore")
|
||||
@ColumnInfo(name = "user_login_id")
|
||||
val userLoginId: Int,
|
||||
|
||||
@ -78,6 +79,13 @@ data class Student(
|
||||
|
||||
@ColumnInfo(name = "registration_date")
|
||||
val registrationDate: Instant,
|
||||
|
||||
@ColumnInfo(name = "is_authorized", defaultValue = "0")
|
||||
val isAuthorized: Boolean,
|
||||
|
||||
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
||||
val isEduOne: Boolean?,
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ -88,3 +96,22 @@ data class Student(
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor = 0L
|
||||
}
|
||||
|
||||
@Entity
|
||||
data class StudentIsAuthorized(
|
||||
|
||||
@PrimaryKey
|
||||
var id: Long,
|
||||
|
||||
@ColumnInfo(name = "is_authorized", defaultValue = "NULL")
|
||||
val isAuthorized: Boolean?,
|
||||
) : Serializable
|
||||
|
||||
@Entity
|
||||
data class StudentIsEduOne(
|
||||
@PrimaryKey
|
||||
var id: Long,
|
||||
|
||||
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
||||
val isEduOne: Boolean?,
|
||||
) : Serializable
|
||||
|
@ -0,0 +1,11 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration63 : AutoMigrationSpec {
|
||||
|
||||
override fun onPostMigrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
|
||||
}
|
||||
}
|
@ -7,6 +7,6 @@ enum class AppTheme(val value: String) {
|
||||
BLACK("black");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = values().find { it.value == value } ?: LIGHT
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: LIGHT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class AttendanceCalculatorSortingMode(private val value: String) {
|
||||
ALPHABETIC("alphabetic"),
|
||||
ATTENDANCE("attendance_percentage"),
|
||||
LESSON_BALANCE("lesson_balance");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) =
|
||||
AttendanceCalculatorSortingMode.values()
|
||||
.find { it.value == value } ?: ALPHABETIC
|
||||
}
|
||||
}
|
@ -8,6 +8,6 @@ enum class GradeColorTheme(val value: String) : Serializable {
|
||||
GRADE_COLOR("grade_color");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = values().find { it.value == value } ?: VULCAN
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: VULCAN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ enum class GradeExpandMode(val value: String) {
|
||||
ALWAYS_EXPANDED("always");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = values().find { it.value == value } ?: ONE
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: ONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ enum class GradeSortingMode(val value: String) {
|
||||
AVERAGE("average");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: ALPHABETIC
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ enum class MessageType {
|
||||
GENERAL_MESSAGE,
|
||||
DASHBOARD_MESSAGE,
|
||||
LOGIN_MESSAGE,
|
||||
LOGIN_STUDENT_SELECT_MESSAGE,
|
||||
LOGIN_SYMBOL_MESSAGE,
|
||||
PASS_RESET_MESSAGE,
|
||||
ERROR_OVERRIDE,
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class ShowAdditionalLessonsMode(val value: String) {
|
||||
NONE("none"),
|
||||
INLINE("inline"),
|
||||
BELOW("below");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE
|
||||
}
|
||||
}
|
@ -6,6 +6,6 @@ enum class TimetableMode(val value: String) {
|
||||
SMALL_OTHER_GROUP("small");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = values().find { it.value == value } ?: ONLY_CURRENT_GROUP
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: ONLY_CURRENT_GROUP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,26 @@ package io.github.wulkanowy.data.mappers
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
|
||||
import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
|
||||
|
||||
@JvmName("mapDirectorInformationToEntities")
|
||||
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
||||
SchoolAnnouncement(
|
||||
userLoginId = student.userLoginId,
|
||||
studentId = student.studentId,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
content = it.content,
|
||||
author = null,
|
||||
)
|
||||
}
|
||||
|
||||
@JvmName("mapLastAnnouncementsToEntities")
|
||||
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
|
||||
SchoolAnnouncement(
|
||||
studentId = student.studentId,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
content = it.content,
|
||||
author = it.author,
|
||||
)
|
||||
}
|
||||
|
@ -37,9 +37,11 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
|
||||
predictedGrade = it.predicted,
|
||||
finalGrade = it.final,
|
||||
pointsSum = it.pointsSum,
|
||||
pointsSumAllYear = it.pointsSumAllYear,
|
||||
proposedPoints = it.proposedPoints,
|
||||
finalPoints = it.finalPoints,
|
||||
average = it.average
|
||||
average = it.average,
|
||||
averageAllYear = it.averageAllYear,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
|
||||
|
||||
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
||||
MobileDevice(
|
||||
userLoginId = student.userLoginId,
|
||||
studentId = student.studentId,
|
||||
date = it.createDate.toInstant(),
|
||||
deviceId = it.id,
|
||||
name = it.name
|
||||
|
@ -34,17 +34,19 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
|
||||
error = it.error,
|
||||
students = it.subjects
|
||||
.filterIsInstance<SdkRegisterStudent>()
|
||||
.map { registerSubject ->
|
||||
.map { registerStudent ->
|
||||
RegisterStudent(
|
||||
studentId = registerSubject.studentId,
|
||||
studentName = registerSubject.studentName,
|
||||
studentSecondName = registerSubject.studentSecondName,
|
||||
studentSurname = registerSubject.studentSurname,
|
||||
className = registerSubject.className,
|
||||
classId = registerSubject.classId,
|
||||
isParent = registerSubject.isParent,
|
||||
semesters = registerSubject.semesters
|
||||
.mapToEntities(registerSubject.studentId),
|
||||
studentId = registerStudent.studentId,
|
||||
studentName = registerStudent.studentName,
|
||||
studentSecondName = registerStudent.studentSecondName,
|
||||
studentSurname = registerStudent.studentSurname,
|
||||
className = registerStudent.className,
|
||||
classId = registerStudent.classId,
|
||||
isParent = registerStudent.isParent,
|
||||
isAuthorized = registerStudent.isAuthorized,
|
||||
isEduOne = registerStudent.isEduOne,
|
||||
semesters = registerStudent.semesters
|
||||
.mapToEntities(registerStudent.studentId),
|
||||
)
|
||||
},
|
||||
)
|
||||
@ -84,6 +86,8 @@ fun RegisterStudent.mapToStudentWithSemesters(
|
||||
password = user.password.orEmpty(),
|
||||
isCurrent = false,
|
||||
registrationDate = Instant.now(),
|
||||
isAuthorized = this.isAuthorized,
|
||||
isEduOne = this.isEduOne,
|
||||
).apply {
|
||||
avatarColor = colors.random()
|
||||
},
|
||||
|
@ -0,0 +1,14 @@
|
||||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
data class AttendanceData(
|
||||
val subjectName: String,
|
||||
val lessonBalance: Int,
|
||||
val presences: Int,
|
||||
val absences: Int,
|
||||
) {
|
||||
val total: Int
|
||||
get() = presences + absences
|
||||
|
||||
val presencePercentage: Double
|
||||
get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100
|
||||
}
|
@ -45,4 +45,6 @@ data class RegisterStudent(
|
||||
val classId: Int,
|
||||
val isParent: Boolean,
|
||||
val semesters: List<Semester>,
|
||||
val isAuthorized: Boolean,
|
||||
val isEduOne: Boolean
|
||||
) : java.io.Serializable
|
||||
|
@ -1,33 +0,0 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AdminMessageRepository @Inject constructor(
|
||||
private val adminMessageService: AdminMessageService,
|
||||
private val adminMessageDao: AdminMessageDao,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { false },
|
||||
query = { adminMessageDao.loadAll() },
|
||||
fetch = { adminMessageService.getAdminMessages() },
|
||||
shouldFetch = { true },
|
||||
saveFetchResult = { oldItems, newItems ->
|
||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||
},
|
||||
showSavedOnLoading = false,
|
||||
)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
@ -7,16 +8,16 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Absent
|
||||
import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -28,14 +29,73 @@ import javax.inject.Singleton
|
||||
class AttendanceRepository @Inject constructor(
|
||||
private val attendanceDb: AttendanceDao,
|
||||
private val timetableDb: TimetableDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
private fun filterAttendance(
|
||||
hiddenAttendanceTiles: List<DashboardItem.HiddenAttendanceTile>,
|
||||
attendanceItem: Attendance
|
||||
): Boolean {
|
||||
return when {
|
||||
attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE
|
||||
) -> false
|
||||
|
||||
attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE
|
||||
) -> false
|
||||
|
||||
attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS
|
||||
) -> false
|
||||
|
||||
attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS
|
||||
) -> false
|
||||
|
||||
attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false
|
||||
attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false
|
||||
attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false
|
||||
|
||||
else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterAttendance(
|
||||
hiddenAttendanceTiles: List<DashboardItem.HiddenAttendanceTile>,
|
||||
attendanceItem: SdkAttendance
|
||||
): Boolean {
|
||||
return when {
|
||||
attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE
|
||||
) -> false
|
||||
|
||||
attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE
|
||||
) -> false
|
||||
|
||||
attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS
|
||||
) -> false
|
||||
|
||||
attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains(
|
||||
DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS
|
||||
) -> false
|
||||
|
||||
attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false
|
||||
attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false
|
||||
attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false
|
||||
|
||||
else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAttendance(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
@ -53,24 +113,34 @@ class AttendanceRepository @Inject constructor(
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
|
||||
|
||||
attendanceDb
|
||||
.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
.map {
|
||||
it.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
|
||||
}
|
||||
},
|
||||
fetch = {
|
||||
val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
|
||||
|
||||
val lessons = timetableDb.load(
|
||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||
)
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getAttendance(start.monday, end.sunday)
|
||||
.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
|
||||
.mapToEntities(semester, lessons)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
||||
newAttendance.apply { if (notify) isNotified = false }
|
||||
}
|
||||
attendanceDb.insertAll(attendanceToAdd)
|
||||
|
||||
attendanceDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = attendanceToAdd,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
@ -81,7 +151,13 @@ class AttendanceRepository @Inject constructor(
|
||||
start: LocalDate,
|
||||
end: LocalDate
|
||||
): Flow<List<Attendance>> {
|
||||
return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end)
|
||||
val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
|
||||
|
||||
return attendanceDb
|
||||
.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
.map {
|
||||
it.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTimetable(timetable: List<Attendance>) {
|
||||
@ -89,8 +165,10 @@ class AttendanceRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun excuseForAbsence(
|
||||
student: Student, semester: Semester,
|
||||
absenceList: List<Attendance>, reason: String? = null
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
absenceList: List<Attendance>,
|
||||
reason: String? = null
|
||||
) {
|
||||
val items = absenceList.map { attendance ->
|
||||
Absent(
|
||||
@ -98,8 +176,7 @@ class AttendanceRepository @Inject constructor(
|
||||
timeId = attendance.timeId
|
||||
)
|
||||
}
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.excuseForAbsence(items, reason)
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -18,8 +18,9 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class AttendanceSummaryRepository @Inject constructor(
|
||||
private val attendanceDb: AttendanceSummaryDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val appDatabase: AppDatabase,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -40,14 +41,15 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
},
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getAttendanceSummary(subjectId)
|
||||
.mapToEntities(semester, subjectId)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
attendanceDb.insertAll(new uniqueSubtract old)
|
||||
appDatabase.withTransaction {
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
attendanceDb.insertAll(new uniqueSubtract old)
|
||||
}
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -1,12 +1,16 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -15,7 +19,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class CompletedLessonsRepository @Inject constructor(
|
||||
private val completedLessonsDb: CompletedLessonsDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -47,14 +51,15 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getCompletedLessons(start.monday, end.sunday)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
completedLessonsDb.deleteAll(old uniqueSubtract new)
|
||||
completedLessonsDb.insertAll(new uniqueSubtract old)
|
||||
completedLessonsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
|
@ -1,16 +1,14 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -21,7 +19,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class ConferenceRepository @Inject constructor(
|
||||
private val conferenceDb: ConferenceDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -46,19 +44,18 @@ class ConferenceRepository @Inject constructor(
|
||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getConferences()
|
||||
.mapToEntities(semester)
|
||||
.filter { it.date >= startDate }
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val conferencesToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
conferenceDb.deleteAll(old uniqueSubtract new)
|
||||
conferenceDb.insertAll(conferencesToSave)
|
||||
conferenceDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -1,18 +1,16 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.endExamsDay
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.startExamsDay
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -23,7 +21,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class ExamRepository @Inject constructor(
|
||||
private val examDb: ExamDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -56,18 +54,17 @@ class ExamRepository @Inject constructor(
|
||||
)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getExams(start.startExamsDay, start.endExamsDay)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val examsToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
examDb.deleteAll(old uniqueSubtract new)
|
||||
examDb.insertAll(examsToSave)
|
||||
examDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||
@ -10,17 +11,15 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.toLocalDate
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,12 +29,20 @@ class GradeRepository @Inject constructor(
|
||||
private val gradeDb: GradeDao,
|
||||
private val gradeSummaryDb: GradeSummaryDao,
|
||||
private val gradeDescriptiveDb: GradeDescriptiveDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private fun loadGrades(semesterId: Int, studentId: Int): Flow<List<Grade>> {
|
||||
val hiddenGrades = preferencesRepository.hiddenGrades
|
||||
|
||||
Timber.i("Load grades for semester $semesterId student $studentId")
|
||||
return gradeDb.loadAllCensored(semesterId, studentId, hiddenGrades.toTypedArray())
|
||||
}
|
||||
|
||||
fun getGrades(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
@ -63,12 +70,15 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
},
|
||||
fetch = {
|
||||
val (details, summary, descriptive) = sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
val hiddenGrades = preferencesRepository.hiddenGrades
|
||||
|
||||
val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester)
|
||||
.getGrades(semester.semesterId)
|
||||
|
||||
val censoredDetails = details.filterNot { it.entry in hiddenGrades }
|
||||
|
||||
Triple(
|
||||
details.mapToEntities(semester),
|
||||
censoredDetails.mapToEntities(semester),
|
||||
summary.mapToEntities(semester),
|
||||
descriptive.mapToEntities(semester)
|
||||
)
|
||||
@ -87,10 +97,12 @@ class GradeRepository @Inject constructor(
|
||||
new: List<GradeDescriptive>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
|
||||
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
})
|
||||
gradeDescriptiveDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeDetails(
|
||||
@ -101,13 +113,16 @@ class GradeRepository @Inject constructor(
|
||||
) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
|
||||
?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
})
|
||||
|
||||
gradeDb.removeOldAndSaveNew(
|
||||
oldItems = oldGrades uniqueSubtract newDetails,
|
||||
newItems = (newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeSummaries(
|
||||
@ -115,41 +130,53 @@ class GradeRepository @Inject constructor(
|
||||
newSummary: List<GradeSummary>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.isFinalGradeNotified = when {
|
||||
summary.finalGrade.isEmpty() -> true
|
||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||
else -> true
|
||||
}
|
||||
gradeSummaryDb.removeOldAndSaveNew(
|
||||
oldItems = oldSummaries uniqueSubtract newSummary,
|
||||
newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
getGradeSummaryWithUpdatedNotificationState(
|
||||
summary = summary,
|
||||
oldSummary = oldSummaries.find { it.subject == summary.subject },
|
||||
notify = notify,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
summary.predictedGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||
else -> oldSummary.predictedGradeLastChange
|
||||
}
|
||||
summary.finalGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
||||
else -> oldSummary.finalGradeLastChange
|
||||
}
|
||||
})
|
||||
private fun getGradeSummaryWithUpdatedNotificationState(
|
||||
summary: GradeSummary,
|
||||
oldSummary: GradeSummary?,
|
||||
notify: Boolean,
|
||||
) {
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.isFinalGradeNotified = when {
|
||||
summary.finalGrade.isEmpty() -> true
|
||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.predictedGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||
else -> oldSummary.predictedGradeLastChange
|
||||
}
|
||||
summary.finalGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
||||
else -> oldSummary.finalGradeLastChange
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
return loadGrades(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isRead }
|
||||
}
|
||||
}
|
||||
|
||||
fun getGradesFromDatabase(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
return loadGrades(semester.semesterId, semester.studentId)
|
||||
}
|
||||
|
||||
fun getGradesPredictedFromDatabase(semester: Semester): Flow<List<GradeSummary>> {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
|
||||
@ -12,14 +13,11 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
|
||||
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -28,7 +26,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val gradePartialStatisticsDb: GradePartialStatisticsDao,
|
||||
private val gradePointsStatisticsDb: GradePointsStatisticsDao,
|
||||
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -56,14 +54,15 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
},
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getGradesPartialStatistics(semester.semesterId)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradePartialStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradePartialStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradePartialStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
@ -80,6 +79,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
listOf(summaryItem) + items
|
||||
}
|
||||
|
||||
else -> items.filter { it.subject == subjectName }
|
||||
}.mapPartialToStatisticItems()
|
||||
}
|
||||
@ -101,14 +101,15 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
},
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getGradesSemesterStatistics(semester.semesterId)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradeSemesterStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
@ -138,6 +139,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
}
|
||||
listOf(summaryItem) + itemsWithAverage
|
||||
}
|
||||
|
||||
else -> itemsWithAverage.filter { it.subject == subjectName }
|
||||
}.mapSemesterToStatisticItems()
|
||||
}
|
||||
@ -157,14 +159,15 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
},
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getGradesPointsStatistics(semester.semesterId)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradePointsStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradePointsStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradePointsStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
|
@ -1,18 +1,16 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
@ -22,7 +20,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class HomeworkRepository @Inject constructor(
|
||||
private val homeworkDb: HomeworkDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -55,20 +53,19 @@ class HomeworkRepository @Inject constructor(
|
||||
)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getHomework(start.monday, end.sunday)
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||
|
||||
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||
homeworkDb.insertAll(homeWorkToSave)
|
||||
|
||||
homeworkDb.removeOldAndSaveNew(
|
||||
oldItems = filteredOld uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
}
|
||||
)
|
||||
|
@ -1,12 +1,13 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
|
||||
import io.github.wulkanowy.utils.AppWidgetUpdater
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -18,7 +19,8 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class LuckyNumberRepository @Inject constructor(
|
||||
private val luckyNumberDb: LuckyNumberDao,
|
||||
private val sdk: Sdk
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val appWidgetUpdater: AppWidgetUpdater,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -27,23 +29,28 @@ class LuckyNumberRepository @Inject constructor(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
isFromAppWidget: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { it == null },
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = {
|
||||
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.getLuckyNumber(student.schoolShortName)
|
||||
?.mapToEntity(student)
|
||||
},
|
||||
saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
|
||||
newLuckyNumber ?: return@networkBoundResource
|
||||
|
||||
if (newLuckyNumber != oldLuckyNumber) {
|
||||
val updatedLuckNumberList =
|
||||
listOf(newLuckyNumber.apply { if (notify) isNotified = false })
|
||||
|
||||
oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
luckyNumberDb.insertAll(updatedLuckNumberList)
|
||||
luckyNumberDb.removeOldAndSaveNew(
|
||||
oldItems = listOfNotNull(oldLuckyNumber),
|
||||
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
|
||||
)
|
||||
if (!isFromAppWidget) {
|
||||
appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
@ -29,11 +30,9 @@ import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Folder
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -48,7 +47,7 @@ class MessageRepository @Inject constructor(
|
||||
private val messagesDb: MessagesDao,
|
||||
private val mutedMessageSendersDao: MutedMessageSendersDao,
|
||||
private val messageAttachmentDao: MessageAttachmentDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sharedPrefProvider: SharedPrefProvider,
|
||||
@ -82,19 +81,26 @@ class MessageRepository @Inject constructor(
|
||||
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).getMessages(
|
||||
folder = Folder.valueOf(folder.name),
|
||||
mailboxKey = mailbox?.globalKey,
|
||||
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
||||
wulkanowySdkFactory.create(student)
|
||||
.getMessages(
|
||||
folder = Folder.valueOf(folder.name),
|
||||
mailboxKey = mailbox?.globalKey,
|
||||
)
|
||||
.mapToEntities(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
allMailboxes = mailboxDao.loadAll(student.email)
|
||||
)
|
||||
},
|
||||
saveFetchResult = { oldWithAuthors, new ->
|
||||
val old = oldWithAuthors.map { it.message }
|
||||
messagesDb.deleteAll(old uniqueSubtract new)
|
||||
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
||||
val muted = isMuted(it.correspondents)
|
||||
it.isNotified = !notify || muted
|
||||
})
|
||||
|
||||
messagesDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
val muted = isMuted(it.correspondents)
|
||||
it.isNotified = !notify || muted
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(
|
||||
getRefreshKey(messagesCacheKey, mailbox, folder)
|
||||
)
|
||||
@ -114,10 +120,11 @@ class MessageRepository @Inject constructor(
|
||||
},
|
||||
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||
fetch = {
|
||||
sdk.init(student).getMessageDetails(
|
||||
messageKey = it!!.message.messageGlobalKey,
|
||||
markAsRead = message.unread && markAsRead,
|
||||
)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.getMessageDetails(
|
||||
messageKey = message.messageGlobalKey,
|
||||
markAsRead = message.unread && markAsRead,
|
||||
)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
checkNotNull(old) { "Fetched message no longer exist!" }
|
||||
@ -158,19 +165,19 @@ class MessageRepository @Inject constructor(
|
||||
recipients: List<Recipient>,
|
||||
mailbox: Mailbox,
|
||||
) {
|
||||
sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients.mapFromEntities(),
|
||||
mailboxId = mailbox.globalKey,
|
||||
)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients.mapFromEntities(),
|
||||
mailboxId = mailbox.globalKey,
|
||||
)
|
||||
refreshFolders(student, mailbox, listOf(SENT))
|
||||
}
|
||||
|
||||
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
sdk.init(student).restoreMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.restoreMessages(messages = messages.map { it.messageGlobalKey })
|
||||
|
||||
refreshFolders(student, mailbox)
|
||||
}
|
||||
@ -181,10 +188,11 @@ class MessageRepository @Inject constructor(
|
||||
|
||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||
val firstMessage = messages.first()
|
||||
sdk.init(student).deleteMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
removeForever = firstMessage.folderId == TRASHED.id,
|
||||
)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.deleteMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
removeForever = firstMessage.folderId == TRASHED.id,
|
||||
)
|
||||
|
||||
if (firstMessage.folderId != TRASHED.id) {
|
||||
val deletedMessages = messages.map {
|
||||
@ -229,7 +237,9 @@ class MessageRepository @Inject constructor(
|
||||
},
|
||||
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
|
||||
fetch = {
|
||||
sdk.init(student).getMailboxes().mapToEntities(student)
|
||||
wulkanowySdkFactory.create(student)
|
||||
.getMailboxes()
|
||||
.mapToEntities(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
mailboxDao.deleteAll(old uniqueSubtract new)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
@ -8,11 +9,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -21,7 +19,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class MobileDeviceRepository @Inject constructor(
|
||||
private val mobileDb: MobileDeviceDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -40,32 +38,30 @@ class MobileDeviceRepository @Inject constructor(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { mobileDb.loadAll(student.userLoginId) },
|
||||
query = { mobileDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getRegisteredDevices()
|
||||
.mapToEntities(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
mobileDb.deleteAll(old uniqueSubtract new)
|
||||
mobileDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
mobileDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.unregisterDevice(device.deviceId)
|
||||
|
||||
mobileDb.deleteAll(listOf(device))
|
||||
}
|
||||
|
||||
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
|
||||
return sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
return wulkanowySdkFactory.create(student, semester)
|
||||
.getToken()
|
||||
.mapToMobileDeviceToken()
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.toLocalDate
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -16,8 +19,9 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class NoteRepository @Inject constructor(
|
||||
private val noteDb: NoteDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -36,24 +40,29 @@ class NoteRepository @Inject constructor(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, semester)
|
||||
)
|
||||
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
val showNotes = preferencesRepository.showNotes
|
||||
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getNotes()
|
||||
.filter { showNotes }
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
noteDb.deleteAll(old uniqueSubtract new)
|
||||
noteDb.insertAll((new uniqueSubtract old).onEach {
|
||||
val notesToAdd = (new uniqueSubtract old).onEach {
|
||||
if (it.date >= student.registrationDate.toLocalDate()) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
noteDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = notesToAdd,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -7,12 +7,16 @@ import androidx.core.content.edit
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.fredporciuncula.flow.preferences.Preference
|
||||
import com.fredporciuncula.flow.preferences.Serializer
|
||||
import com.fredporciuncula.flow.preferences.map
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.api.models.Mapping
|
||||
import io.github.wulkanowy.data.enums.AppTheme
|
||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
|
||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||
import io.github.wulkanowy.data.enums.GradeExpandMode
|
||||
import io.github.wulkanowy.data.enums.GradeSortingMode
|
||||
import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode
|
||||
import io.github.wulkanowy.data.enums.TimetableMode
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
@ -22,6 +26,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
@ -34,6 +39,7 @@ class PreferencesRepository @Inject constructor(
|
||||
private val flowSharedPref: FlowSharedPreferences,
|
||||
private val json: Json,
|
||||
) {
|
||||
private val NO_ATTENDANCE_VALUE = -1.0
|
||||
|
||||
val isShowPresent: Boolean
|
||||
get() = getBoolean(
|
||||
@ -41,6 +47,27 @@ class PreferencesRepository @Inject constructor(
|
||||
R.bool.pref_default_attendance_present
|
||||
)
|
||||
|
||||
val targetAttendanceFlow: Flow<Int>
|
||||
get() = flowSharedPref.getInt(
|
||||
context.getString(R.string.pref_key_attendance_target),
|
||||
context.resources.getInteger(R.integer.pref_default_attendance_target)
|
||||
).asFlow()
|
||||
|
||||
val attendanceCalculatorSortingModeFlow: Flow<AttendanceCalculatorSortingMode>
|
||||
get() = flowSharedPref.getString(
|
||||
context.getString(R.string.pref_key_attendance_calculator_sorting_mode),
|
||||
context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode)
|
||||
).asFlow().map(AttendanceCalculatorSortingMode::getByValue)
|
||||
|
||||
/**
|
||||
* Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0).
|
||||
*/
|
||||
val attendanceCalculatorShowEmptySubjects: Flow<Boolean>
|
||||
get() = flowSharedPref.getBoolean(
|
||||
context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects),
|
||||
context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects)
|
||||
).asFlow()
|
||||
|
||||
private val gradeAverageModePref: Preference<GradeAverageMode>
|
||||
get() = getObjectFlow(
|
||||
R.string.pref_key_grade_average_mode,
|
||||
@ -191,6 +218,12 @@ class PreferencesRepository @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode
|
||||
get() = getString(
|
||||
R.string.pref_key_timetable_show_additional_lessons,
|
||||
R.string.pref_default_timetable_show_additional_lessons
|
||||
).let { ShowAdditionalLessonsMode.getByValue(it) }
|
||||
|
||||
val gradeSortingMode: GradeSortingMode
|
||||
get() = GradeSortingMode.getByValue(
|
||||
getString(
|
||||
@ -272,6 +305,60 @@ class PreferencesRepository @Inject constructor(
|
||||
selectedDashboardTilesPreference.set(filteredValue)
|
||||
}
|
||||
|
||||
var attendancePercentage: Double?
|
||||
get() = attendancePercentagePreference.get().takeIf { it != NO_ATTENDANCE_VALUE }
|
||||
set(value) = attendancePercentagePreference.set(value ?: NO_ATTENDANCE_VALUE)
|
||||
|
||||
var hiddenAttendanceItems: List<DashboardItem.HiddenAttendanceTile>
|
||||
get() = hiddenAttendanceItemsPreference.get().toList()
|
||||
set(value) = hiddenAttendanceItemsPreference.set(value.toSet())
|
||||
|
||||
var hiddenGrades: List<String>
|
||||
get() = hiddenGradesPreference.get().toList()
|
||||
set(value) = hiddenGradesPreference.set(value.toSet())
|
||||
|
||||
var showNotes: Boolean
|
||||
get() = showNotesPreference.get()
|
||||
set(value) = showNotesPreference.set(value)
|
||||
|
||||
var developerMode: Boolean
|
||||
get() = developerModePreference.get()
|
||||
set(value) = developerModePreference.set(value)
|
||||
|
||||
private val developerModePreference: Preference<Boolean>
|
||||
get() = flowSharedPref.getBoolean(
|
||||
context.getString(R.string.pref_key_developer_mode),
|
||||
context.resources.getBoolean(R.bool.pref_default_developer_mode)
|
||||
)
|
||||
|
||||
private val hiddenGradesPreference: Preference<Set<String>>
|
||||
get() {
|
||||
val defaultSet = context.resources.getStringArray(R.array.pref_default_hidden_grades).toSet()
|
||||
val prefKey = "hidden_grades"
|
||||
|
||||
return flowSharedPref.getStringSet(prefKey, defaultSet)
|
||||
}
|
||||
|
||||
private val showNotesPreference: Preference<Boolean>
|
||||
get() = flowSharedPref.getBoolean(
|
||||
context.getString(R.string.pref_key_show_notes),
|
||||
context.resources.getBoolean(R.bool.pref_default_show_notes)
|
||||
)
|
||||
|
||||
private val hiddenAttendanceItemsPreference: Preference<Set<DashboardItem.HiddenAttendanceTile>>
|
||||
get() {
|
||||
val defaultSet =
|
||||
context.resources.getStringArray(R.array.pref_default_hidden_attendance_items).toSet()
|
||||
val prefKey = "attendance_items"
|
||||
|
||||
return flowSharedPref
|
||||
.getStringSet(prefKey, defaultSet)
|
||||
.map(
|
||||
mapper = { it -> it.map { DashboardItem.HiddenAttendanceTile.valueOf(it) }.toSet() },
|
||||
reverse = { it -> it.map { it.name }.toSet() }
|
||||
)
|
||||
}
|
||||
|
||||
private val selectedDashboardTilesPreference: Preference<Set<String>>
|
||||
get() {
|
||||
val defaultSet =
|
||||
@ -281,6 +368,19 @@ class PreferencesRepository @Inject constructor(
|
||||
return flowSharedPref.getStringSet(prefKey, defaultSet)
|
||||
}
|
||||
|
||||
private val attendancePercentagePreference: Preference<Double>
|
||||
get() {
|
||||
val prefKey = context.getString(R.string.pref_key_attendance_percentage)
|
||||
val defaultValue = context.resources.getString(R.string.pref_default_attendance_percentage)
|
||||
|
||||
return flowSharedPref
|
||||
.getString(prefKey, defaultValue)
|
||||
.map(
|
||||
mapper = { it.toDoubleOrNull() ?: NO_ATTENDANCE_VALUE },
|
||||
reverse = { it.toString() }
|
||||
)
|
||||
}
|
||||
|
||||
var dismissedAdminMessageIds: List<Int>
|
||||
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
|
||||
.orEmpty()
|
||||
@ -346,6 +446,15 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
||||
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
|
||||
|
||||
var mapping: Mapping?
|
||||
get() {
|
||||
val value = sharedPref.getString("mapping", null)
|
||||
return value?.let { json.decodeFromString(it) }
|
||||
}
|
||||
set(value) = sharedPref.edit(commit = true) {
|
||||
putString("mapping", value?.let { json.encodeToString(it) })
|
||||
}
|
||||
|
||||
init {
|
||||
if (installationId.isEmpty()) {
|
||||
installationId = UUID.randomUUID().toString()
|
||||
|
@ -1,12 +1,15 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -14,19 +17,22 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class RecipientRepository @Inject constructor(
|
||||
private val recipientDb: RecipientDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val cacheKey = "recipient"
|
||||
|
||||
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
|
||||
val new = sdk.init(student).getRecipients(mailbox.globalKey)
|
||||
val new = wulkanowySdkFactory.create(student)
|
||||
.getRecipients(mailbox.globalKey)
|
||||
.mapToEntities(mailbox.globalKey)
|
||||
val old = recipientDb.loadAll(type, mailbox.globalKey)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
recipientDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
@ -54,7 +60,7 @@ class RecipientRepository @Inject constructor(
|
||||
): List<Recipient> {
|
||||
mailbox ?: return emptyList()
|
||||
|
||||
return sdk.init(student)
|
||||
return wulkanowySdkFactory.create(student)
|
||||
.getMessageReplayDetails(message.messageGlobalKey)
|
||||
.sender
|
||||
.let(::listOf)
|
||||
|
@ -1,17 +1,23 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
||||
class RecoverRepository @Inject constructor(
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory
|
||||
) {
|
||||
|
||||
suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> {
|
||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||
}
|
||||
suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> =
|
||||
wulkanowySdkFactory.create()
|
||||
.getPasswordResetCaptchaCode(host, symbol)
|
||||
|
||||
suspend fun sendRecoverRequest(
|
||||
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
url: String,
|
||||
symbol: String,
|
||||
email: String,
|
||||
reCaptchaResponse: String
|
||||
): String = wulkanowySdkFactory.create()
|
||||
.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -18,7 +17,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SchoolAnnouncementRepository @Inject constructor(
|
||||
private val schoolAnnouncementDb: SchoolAnnouncementDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -38,26 +37,27 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||
schoolAnnouncementDb.loadAll(student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.getDirectorInformation()
|
||||
.mapToEntities(student)
|
||||
val sdk = wulkanowySdkFactory.create(student)
|
||||
val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student)
|
||||
val directorInformation = sdk.getDirectorInformation().mapToEntities(student)
|
||||
lastAnnouncements + directorInformation
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
|
||||
schoolAnnouncementDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||
return schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||
}
|
||||
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||
|
@ -1,15 +1,13 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -17,7 +15,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SchoolRepository @Inject constructor(
|
||||
private val schoolDb: SchoolDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -40,17 +38,16 @@ class SchoolRepository @Inject constructor(
|
||||
},
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
schoolDb.removeOldAndSaveNew(
|
||||
oldItems = listOf(old),
|
||||
newItems = listOf(new)
|
||||
)
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.api.SchoolsService
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.utils.IntegrityHelper
|
||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
@ -23,7 +21,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
class SchoolsRepository @Inject constructor(
|
||||
private val integrityHelper: IntegrityHelper,
|
||||
private val schoolsService: SchoolsService,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
) {
|
||||
|
||||
suspend fun logSchoolLogin(loginData: LoginData, students: List<StudentWithSemesters>) {
|
||||
@ -40,10 +38,9 @@ class SchoolsRepository @Inject constructor(
|
||||
private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val token = integrityHelper.getIntegrityToken(requestId) ?: return
|
||||
val updatedStudent = student.copy(password = loginData.password)
|
||||
|
||||
val schoolInfo = sdk
|
||||
.init(student.copy(password = loginData.password))
|
||||
.switchSemester(semester)
|
||||
val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester)
|
||||
.getSchool()
|
||||
|
||||
schoolsService.logLoginEvent(
|
||||
|
@ -1,11 +1,15 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||
import io.github.wulkanowy.utils.isCurrent
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -14,8 +18,8 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SemesterRepository @Inject constructor(
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val dispatchers: DispatchersProvider
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val dispatchers: DispatchersProvider,
|
||||
) {
|
||||
|
||||
suspend fun getSemesters(
|
||||
@ -45,6 +49,7 @@ class SemesterRepository @Inject constructor(
|
||||
0 == it.diaryId && 0 == it.kindergartenDiaryId
|
||||
} == true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
@ -55,12 +60,20 @@ class SemesterRepository @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun refreshSemesters(student: Student) {
|
||||
val new = sdk.init(student).getSemesters().mapToEntities(student.studentId)
|
||||
if (new.isEmpty()) return Timber.i("Empty semester list!")
|
||||
val new = wulkanowySdkFactory.create(student)
|
||||
.getSemesters()
|
||||
.mapToEntities(student.studentId)
|
||||
|
||||
if (new.isEmpty()) {
|
||||
Timber.i("Empty semester list from SDK!")
|
||||
return
|
||||
}
|
||||
|
||||
val old = semesterDb.loadAll(student.studentId, student.classId)
|
||||
semesterDb.deleteAll(old.uniqueSubtract(new))
|
||||
semesterDb.insertSemesters(new.uniqueSubtract(old))
|
||||
semesterDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
|
@ -1,13 +1,11 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -15,7 +13,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class StudentInfoRepository @Inject constructor(
|
||||
private val studentInfoDao: StudentInfoDao,
|
||||
private val sdk: Sdk
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -30,16 +28,16 @@ class StudentInfoRepository @Inject constructor(
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getStudentInfo()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
studentInfoDao.removeOldAndSaveNew(
|
||||
oldItems = listOf(old),
|
||||
newItems = listOf(new),
|
||||
)
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
|
@ -1,23 +1,25 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
|
||||
import io.github.wulkanowy.data.db.entities.StudentName
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToPojo
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.security.Scrambler
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -26,7 +28,7 @@ class StudentRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val appDatabase: AppDatabase,
|
||||
private val scrambler: Scrambler,
|
||||
) {
|
||||
@ -37,9 +39,10 @@ class StudentRepository @Inject constructor(
|
||||
pin: String,
|
||||
symbol: String,
|
||||
token: String
|
||||
): RegisterUser = sdk
|
||||
): RegisterUser = wulkanowySdkFactory.create()
|
||||
.getStudentsFromHebe(token, pin, symbol, "")
|
||||
.mapToPojo(null)
|
||||
.also { it.logErrors() }
|
||||
|
||||
suspend fun getUserSubjectsFromScrapper(
|
||||
email: String,
|
||||
@ -47,18 +50,20 @@ class StudentRepository @Inject constructor(
|
||||
scrapperBaseUrl: String,
|
||||
domainSuffix: String,
|
||||
symbol: String
|
||||
): RegisterUser = sdk
|
||||
): RegisterUser = wulkanowySdkFactory.create()
|
||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
||||
.mapToPojo(password)
|
||||
.also { it.logErrors() }
|
||||
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): RegisterUser = sdk
|
||||
): RegisterUser = wulkanowySdkFactory.create()
|
||||
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||
.mapToPojo(password)
|
||||
.also { it.logErrors() }
|
||||
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
||||
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
||||
@ -100,6 +105,46 @@ class StudentRepository @Inject constructor(
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun updateCurrentStudentAuthStatus() {
|
||||
Timber.i("Check isAuthorized: started")
|
||||
val student = getCurrentStudent()
|
||||
if (student.isAuthorized) {
|
||||
Timber.i("Check isAuthorized: already authorized")
|
||||
return
|
||||
}
|
||||
|
||||
val initializedSdk = wulkanowySdkFactory.create(student)
|
||||
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
||||
.onFailure { Timber.e(it, "Check isAuthorized: error occurred") }
|
||||
.getOrNull()
|
||||
|
||||
if (newCurrentStudent == null) {
|
||||
Timber.d("Check isAuthorized: current user is null")
|
||||
return
|
||||
}
|
||||
|
||||
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
if (currentStudentSemesters.isEmpty()) {
|
||||
Timber.d("Check isAuthorized: apply empty semesters workaround")
|
||||
semesterDb.insertSemesters(
|
||||
items = newCurrentStudent.semesters.mapToEntities(student.studentId),
|
||||
)
|
||||
}
|
||||
|
||||
if (!newCurrentStudent.isAuthorized) {
|
||||
Timber.i("Check isAuthorized: authorization required")
|
||||
throw NoAuthorizationException()
|
||||
}
|
||||
|
||||
val studentIsAuthorized = StudentIsAuthorized(
|
||||
id = student.id,
|
||||
isAuthorized = true
|
||||
)
|
||||
|
||||
Timber.i("Check isAuthorized: already authorized, update local status")
|
||||
studentDb.update(studentIsAuthorized)
|
||||
}
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||
|
||||
@ -149,20 +194,24 @@ class StudentRepository @Inject constructor(
|
||||
.distinctBy { it.student.studentName }.size == 1
|
||||
|
||||
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.authorizePermission(pesel)
|
||||
|
||||
suspend fun refreshStudentName(student: Student, semester: Semester) {
|
||||
val newCurrentApiStudent = sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
.getCurrentStudent() ?: return
|
||||
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
|
||||
val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
|
||||
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
|
||||
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
|
||||
.getOrNull() ?: return
|
||||
|
||||
val studentName = StudentName(
|
||||
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
|
||||
).apply { id = student.id }
|
||||
|
||||
studentDb.update(studentName)
|
||||
semesterDb.removeOldAndSaveNew(
|
||||
oldItems = semesterDb.loadAll(student.studentId, semester.classId),
|
||||
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
||||
@ -175,4 +224,18 @@ class StudentRepository @Inject constructor(
|
||||
appDatabase.clearAllTables()
|
||||
}
|
||||
}
|
||||
|
||||
private fun RegisterUser.logErrors() {
|
||||
val symbolsErrors = symbols.filter { it.error != null }
|
||||
.map { it.error }
|
||||
val unitsErrors = symbols.flatMap { it.schools }
|
||||
.filter { it.error != null }
|
||||
.map { it.error }
|
||||
|
||||
(symbolsErrors + unitsErrors).forEach { error ->
|
||||
Timber.e(error, "Error occurred while fetching students")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoAuthorizationException : Exception()
|
||||
|
@ -1,15 +1,13 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -18,7 +16,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val subjectDao: SubjectDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -39,15 +37,15 @@ class SubjectRepository @Inject constructor(
|
||||
},
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getSubjects()
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
subjectDao.insertAll(new uniqueSubtract old)
|
||||
|
||||
subjectDao.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -1,15 +1,13 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -18,7 +16,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class TeacherRepository @Inject constructor(
|
||||
private val teacherDb: TeacherDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
@ -39,15 +37,15 @@ class TeacherRepository @Inject constructor(
|
||||
},
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getTeachers()
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
teacherDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||
@ -11,14 +12,13 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
|
||||
import io.github.wulkanowy.utils.AppWidgetUpdater
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@ -28,14 +28,16 @@ import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@Singleton
|
||||
class TimetableRepository @Inject constructor(
|
||||
private val timetableDb: TimetableDao,
|
||||
private val timetableAdditionalDb: TimetableAdditionalDao,
|
||||
private val timetableHeaderDb: TimetableHeaderDao,
|
||||
private val sdk: Sdk,
|
||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||
private val schedulerHelper: TimetableNotificationSchedulerHelper,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val appWidgetUpdater: AppWidgetUpdater,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -54,7 +56,8 @@ class TimetableRepository @Inject constructor(
|
||||
forceRefresh: Boolean,
|
||||
refreshAdditional: Boolean = false,
|
||||
notify: Boolean = false,
|
||||
timetableType: TimetableType = TimetableType.NORMAL
|
||||
timetableType: TimetableType = TimetableType.NORMAL,
|
||||
isFromAppWidget: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = {
|
||||
@ -74,8 +77,7 @@ class TimetableRepository @Inject constructor(
|
||||
},
|
||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||
fetch = {
|
||||
val timetableFull = sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
val timetableFull = wulkanowySdkFactory.create(student, semester)
|
||||
.getTimetable(start.monday, end.sunday)
|
||||
|
||||
timetableFull.mapToEntities(semester)
|
||||
@ -86,6 +88,9 @@ class TimetableRepository @Inject constructor(
|
||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
if (!isFromAppWidget) {
|
||||
appWidgetUpdater.updateAllAppWidgetsByProvider(TimetableWidgetProvider::class)
|
||||
}
|
||||
},
|
||||
filterResult = { (timetable, additional, headers) ->
|
||||
TimetableFull(
|
||||
@ -154,8 +159,10 @@ class TimetableRepository @Inject constructor(
|
||||
new.apply { if (notify) isNotified = false }
|
||||
}
|
||||
|
||||
timetableDb.deleteAll(lessonsToRemove)
|
||||
timetableDb.insertAll(lessonsToAdd)
|
||||
timetableDb.removeOldAndSaveNew(
|
||||
oldItems = lessonsToRemove,
|
||||
newItems = lessonsToAdd,
|
||||
)
|
||||
|
||||
schedulerHelper.cancelScheduled(lessonsToRemove, student)
|
||||
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
|
||||
@ -166,13 +173,17 @@ class TimetableRepository @Inject constructor(
|
||||
new: List<TimetableAdditional>
|
||||
) {
|
||||
val oldFiltered = old.filter { !it.isAddedByUser }
|
||||
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
|
||||
timetableAdditionalDb.insertAll(new uniqueSubtract old)
|
||||
timetableAdditionalDb.removeOldAndSaveNew(
|
||||
oldItems = oldFiltered uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
|
||||
timetableHeaderDb.deleteAll(old uniqueSubtract new)
|
||||
timetableHeaderDb.insertAll(new uniqueSubtract old)
|
||||
timetableHeaderDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
|
||||
|
@ -0,0 +1,66 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.api.models.Mapping
|
||||
import io.github.wulkanowy.data.api.services.WulkanowyService
|
||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class WulkanowyRepository @Inject constructor(
|
||||
private val wulkanowyService: WulkanowyService,
|
||||
private val adminMessageDao: AdminMessageDao,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "mapping_refresh_key"
|
||||
|
||||
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { false },
|
||||
query = { adminMessageDao.loadAll() },
|
||||
fetch = { wulkanowyService.getAdminMessages() },
|
||||
shouldFetch = { true },
|
||||
saveFetchResult = { oldItems, newItems ->
|
||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||
},
|
||||
)
|
||||
.filterNot { it is Resource.Intermediate }
|
||||
|
||||
suspend fun getMapping(): Mapping? {
|
||||
var savedMapping = preferencesRepository.mapping
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey)
|
||||
)
|
||||
|
||||
if (savedMapping == null || isExpired) {
|
||||
fetchMapping()
|
||||
savedMapping = preferencesRepository.mapping
|
||||
}
|
||||
|
||||
return savedMapping
|
||||
}
|
||||
|
||||
suspend fun fetchMapping() {
|
||||
runCatching { wulkanowyService.getMapping() }
|
||||
.onFailure { Timber.e(it) }
|
||||
.onSuccess {
|
||||
preferencesRepository.mapping = it
|
||||
refreshHelper.updateLastRefreshTimestamp(cacheKey)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.github.wulkanowy.data.serializers
|
||||
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
object SafeMessageTypeEnumListSerializer : KSerializer<List<MessageType>> {
|
||||
|
||||
private val serializer = ListSerializer(String.serializer())
|
||||
|
||||
override val descriptor = serializer.descriptor
|
||||
|
||||
override fun serialize(encoder: Encoder, value: List<MessageType>) {
|
||||
encoder.encodeNotNullMark()
|
||||
serializer.serialize(encoder, value.map { it.name })
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): List<MessageType> =
|
||||
serializer.deserialize(decoder).mapNotNull { enumName ->
|
||||
MessageType.entries.find { it.name == enumName }
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.data.mapResourceData
|
||||
import io.github.wulkanowy.data.repositories.AdminMessageRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||
private val adminMessageRepository: AdminMessageRepository,
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
@ -22,7 +22,7 @@ class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||
}
|
||||
|
||||
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
|
||||
return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||
return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||
adminMessages
|
||||
.asSequence()
|
||||
.filter { it.isNotDismissed() }
|
||||
|
@ -0,0 +1,106 @@
|
||||
package io.github.wulkanowy.domain.attendance
|
||||
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
|
||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode.*
|
||||
import io.github.wulkanowy.data.pojos.AttendanceData
|
||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SubjectRepository
|
||||
import io.github.wulkanowy.utils.allAbsences
|
||||
import io.github.wulkanowy.utils.allPresences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
class GetAttendanceCalculatorDataUseCase @Inject constructor(
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
): Flow<Resource<List<AttendanceData>>> =
|
||||
subjectRepository.getSubjects(student, semester, forceRefresh)
|
||||
.mapResourceData { subjects -> subjects.sortedBy(Subject::name) }
|
||||
.combineWithResourceData(preferencesRepository.targetAttendanceFlow, ::Pair)
|
||||
.flatMapResourceData { (subjects, targetFreq) ->
|
||||
combineResourceFlows(subjects.map { subject ->
|
||||
attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = subject.realId,
|
||||
forceRefresh = forceRefresh
|
||||
).mapResourceData { summaries ->
|
||||
summaries.toAttendanceData(subject.name, targetFreq)
|
||||
}
|
||||
})
|
||||
// Every individual combined flow causes separate network requests to update data.
|
||||
// When there is N child flows, they can cause up to N-1 items to be emitted. Since all
|
||||
// requests are usually completed in less than 5s, there is no need to emit multiple
|
||||
// intermediates that will be visible for barely any time.
|
||||
.debounceIntermediates()
|
||||
}
|
||||
.combineWithResourceData(preferencesRepository.attendanceCalculatorShowEmptySubjects) { attendanceDataList, showEmptySubjects ->
|
||||
attendanceDataList.filter { it.total != 0 || showEmptySubjects }
|
||||
}
|
||||
.combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List<AttendanceData>::sortedBy)
|
||||
}
|
||||
|
||||
private fun List<AttendanceSummary>.toAttendanceData(subjectName: String, targetFreq: Int): AttendanceData {
|
||||
val presences = sumOf { it.allPresences }
|
||||
val absences = sumOf { it.allAbsences }
|
||||
return AttendanceData(
|
||||
subjectName = subjectName,
|
||||
lessonBalance = calcLessonBalance(
|
||||
targetFreq.toDouble() / 100, presences, absences
|
||||
),
|
||||
presences = presences,
|
||||
absences = absences,
|
||||
)
|
||||
}
|
||||
|
||||
private fun calcLessonBalance(targetFreq: Double, presences: Int, absences: Int): Int {
|
||||
val total = presences + absences
|
||||
// The `+ 1` is to avoid false positives in close cases. Eg.:
|
||||
// target frequency 99%, 1 presence. Without the `+ 1` this would be reported shown as
|
||||
// a positive balance of +1, however that is not actually true as skipping one class
|
||||
// would make it so that the balance would actually be negative (-98). The `+ 1`
|
||||
// fixes this and makes sure that in situations like these, it's not reporting incorrect
|
||||
// balances
|
||||
return when {
|
||||
presences / (total + 1f) >= targetFreq -> calcMissingAbsences(
|
||||
targetFreq, absences, presences
|
||||
)
|
||||
presences / (total + 0f) < targetFreq -> -calcMissingPresences(
|
||||
targetFreq, absences, presences
|
||||
)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun calcMissingPresences(targetFreq: Double, absences: Int, presences: Int) =
|
||||
calcMinRequiredPresencesFor(targetFreq, absences) - presences
|
||||
|
||||
private fun calcMinRequiredPresencesFor(targetFreq: Double, absences: Int) =
|
||||
ceil((targetFreq / (1 - targetFreq)) * absences).toInt()
|
||||
|
||||
private fun calcMissingAbsences(targetFreq: Double, absences: Int, presences: Int) =
|
||||
calcMinRequiredAbsencesFor(targetFreq, presences) - absences
|
||||
|
||||
private fun calcMinRequiredAbsencesFor(targetFreq: Double, presences: Int) =
|
||||
floor((presences * (1 - targetFreq)) / targetFreq).toInt()
|
||||
|
||||
private fun List<AttendanceData>.sortedBy(mode: AttendanceCalculatorSortingMode) = when (mode) {
|
||||
ALPHABETIC -> sortedBy(AttendanceData::subjectName)
|
||||
ATTENDANCE -> sortedByDescending(AttendanceData::presencePercentage)
|
||||
LESSON_BALANCE -> sortedBy(AttendanceData::lessonBalance)
|
||||
}
|
@ -59,7 +59,7 @@ class GetMailboxByStudentUseCase @Inject constructor(
|
||||
private fun String.getUnauthorizedVersion(): String {
|
||||
return normalizeStudentName().split(" ")
|
||||
.joinToString(" ") {
|
||||
it.first() + "*".repeat(it.length - 1)
|
||||
it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException
|
||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||
import io.github.wulkanowy.services.sync.works.Work
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
@ -48,6 +49,7 @@ class SyncWorker @AssistedInject constructor(
|
||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||
student to semester
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e)
|
||||
return@withContext getResultFromErrors(listOf(e))
|
||||
}
|
||||
|
||||
@ -59,7 +61,7 @@ class SyncWorker @AssistedInject constructor(
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) {
|
||||
null
|
||||
} else {
|
||||
Timber.e(e)
|
||||
|
@ -19,16 +19,18 @@ class NewGradeNotification @Inject constructor(
|
||||
) {
|
||||
|
||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items, 1),
|
||||
content = buildString {
|
||||
append("${it.subject}: ${it.entry}")
|
||||
if (it.comment.isNotBlank()) append(" (${it.comment})")
|
||||
},
|
||||
destination = Destination.Grade,
|
||||
)
|
||||
}
|
||||
val notificationDataList = items
|
||||
.filter { !it.isNotified }
|
||||
.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items, 1),
|
||||
content = buildString {
|
||||
append("${it.subject}: ${it.entry}")
|
||||
if (it.comment.isNotBlank()) append(" (${it.comment})")
|
||||
},
|
||||
destination = Destination.Grade,
|
||||
)
|
||||
}
|
||||
|
||||
val groupNotificationData = GroupNotificationData(
|
||||
notificationDataList = notificationDataList,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
@ -17,6 +18,8 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
@ -36,16 +39,26 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
|
||||
abstract var presenter: T
|
||||
|
||||
private var lastDialogOpenTime = mutableMapOf<String, Instant>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
inject()
|
||||
themeManager.applyActivityTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
applyCustomTaskDescription()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(
|
||||
ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))
|
||||
)
|
||||
@Suppress("DEPRECATION")
|
||||
private fun applyCustomTaskDescription() {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return
|
||||
try {
|
||||
val newColor = getThemeAttrColor(R.attr.colorSurface)
|
||||
val taskDescription = ActivityManager.TaskDescription(null, null, newColor)
|
||||
setTaskDescription(taskDescription)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
@ -70,6 +83,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_expired_credentials_title)
|
||||
.setMessage(R.string.main_expired_credentials_description)
|
||||
@ -83,6 +98,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_session_expired)
|
||||
.setMessage(R.string.main_session_relogin)
|
||||
@ -119,4 +136,21 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
protected open fun inject() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun shouldShowDialog(name: String): Boolean {
|
||||
val lastOpenTime = lastDialogOpenTime[name]
|
||||
val now = Instant.now()
|
||||
|
||||
if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) {
|
||||
Timber.i("Dialog $name was shown less than a second ago. Skip")
|
||||
return false
|
||||
}
|
||||
lastDialogOpenTime[name] = Instant.now()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials"
|
||||
private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed"
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.base
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
||||
import io.github.wulkanowy.data.repositories.NoAuthorizationException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
@ -40,7 +40,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
is ScramblerException -> onDecryptionFailed()
|
||||
is BadCredentialsException -> onExpiredCredentials()
|
||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||
is NoAuthorizationException -> onAuthorizationRequired()
|
||||
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,19 @@ package io.github.wulkanowy.ui.modules.about
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.ItemAboutBinding
|
||||
import io.github.wulkanowy.databinding.ScrollableHeaderAboutBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class AboutAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
class AboutAdapter @Inject constructor(
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private var developerModeClicks = 0
|
||||
private enum class ViewType(val id: Int) {
|
||||
ITEM_HEADER(1),
|
||||
ITEM_ELEMENT(2)
|
||||
@ -46,6 +51,19 @@ class AboutAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.Vie
|
||||
|
||||
private fun bindHeaderViewHolder(binding: ScrollableHeaderAboutBinding) {
|
||||
with(binding.aboutScrollableHeaderIcon) {
|
||||
setOnClickListener {
|
||||
if (++developerModeClicks == 5 && !preferencesRepository.developerMode) {
|
||||
preferencesRepository.developerMode = true
|
||||
developerModeClicks = 0
|
||||
|
||||
Toast.makeText(
|
||||
context,
|
||||
"done!",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
setImageDrawable(ResourcesCompat.getDrawableForDensity(
|
||||
context.resources, context.applicationInfo.icon, 640, null)
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -33,17 +34,17 @@ class AttendanceAdapter @Inject constructor() :
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val context = holder.binding.root.context
|
||||
val item = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
attendanceItemNumber.text = item.number.toString()
|
||||
attendanceItemSubject.text = item.subject.ifBlank {
|
||||
root.context.getString(R.string.all_no_data)
|
||||
}
|
||||
attendanceItemSubject.text = item.subject
|
||||
.ifBlank { context.getString(R.string.all_no_data) }
|
||||
attendanceItemDescription.setText(item.descriptionRes)
|
||||
|
||||
attendanceItemDescription.setTextColor(
|
||||
root.context.getThemeAttrColor(
|
||||
context.getThemeAttrColor(
|
||||
when {
|
||||
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||
@ -61,13 +62,15 @@ class AttendanceAdapter @Inject constructor() :
|
||||
attendanceItemAlert.isVisible =
|
||||
item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
|
||||
|
||||
attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
|
||||
when{
|
||||
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||
else -> android.R.attr.colorPrimary
|
||||
}
|
||||
))
|
||||
attendanceItemAlert.imageTintList = ColorStateList.valueOf(
|
||||
context.getThemeAttrColor(
|
||||
when {
|
||||
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||
else -> android.R.attr.colorPrimary
|
||||
}
|
||||
)
|
||||
)
|
||||
attendanceItemNumber.visibility = View.GONE
|
||||
attendanceItemExcuseInfo.visibility = View.GONE
|
||||
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.databinding.DialogExcuseBinding
|
||||
import io.github.wulkanowy.databinding.FragmentAttendanceBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.calculator.AttendanceCalculatorFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
@ -134,6 +135,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected()
|
||||
else if (item.itemId == R.id.attendanceMenuCalculator) presenter.onCalculatorSwitchSelected()
|
||||
else false
|
||||
}
|
||||
|
||||
@ -253,6 +255,10 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openCalculatorView() {
|
||||
(activity as? MainActivity)?.pushView(AttendanceCalculatorFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun startActionMode() {
|
||||
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user