1
0

Compare commits

...

58 Commits
1.1.0 ... 1.1.5

Author SHA1 Message Date
800a31f160 Merge branch 'hotfix/all-year-average' 2021-04-18 20:40:02 +02:00
8b83b37b09 Version 1.1.5 2021-04-18 20:38:18 +02:00
43e95cfdc6 Fix all year average 2021-04-18 19:36:02 +02:00
ae2a697e01 Merge pull request #1295 from wulkanowy/rbo/update-workflow
Update github action workflow
2021-04-18 19:34:32 +02:00
3f5fbbc71b Merge branch 'hotfix/fix-recovery-visibility' 2021-04-01 22:40:20 +02:00
206b40ce1b Change version in changelog 2021-04-01 22:38:35 +02:00
43c56b5534 Version 1.1.4 2021-04-01 22:36:51 +02:00
a1076539dc Fix content visibility in login recovery 2021-04-01 22:30:09 +02:00
3c48264539 Merge branch 'release/1.1.3' 2021-03-28 20:13:37 +02:00
539cf2207b Version 1.1.3 2021-03-28 20:13:32 +02:00
f0e897713c New Crowdin updates (#1233) 2021-03-28 19:52:38 +02:00
a448092008 Allow special login format in login form (#1258) 2021-03-28 19:46:56 +02:00
f5b46707ff Fixing README.md (#1255) 2021-03-28 15:56:44 +02:00
e6247d4428 Fix clearing no existing dialog fragment (#1242) 2021-03-28 08:18:07 +02:00
d0869b235a Use db student id to distinguish reporting units and recipients (#1254) 2021-03-27 13:43:25 +01:00
464900d95b Bump annotation from 1.1.0 to 1.2.0 (#1245) 2021-03-25 10:12:05 +00:00
368274239e Bump agconnect-crash from 1.5.0.300 to 1.5.1.200 (#1247) 2021-03-25 10:11:19 +00:00
ee33197494 Bump room from 2.3.0-beta03 to 2.3.0-rc01 (#1246) 2021-03-25 10:09:21 +00:00
d3ea743707 Bump kotlin_version from 1.4.31 to 1.4.32 (#1253) 2021-03-25 10:07:43 +00:00
976d4b8ce2 Bump fragment-ktx from 1.3.1 to 1.3.2 (#1248) 2021-03-25 11:05:14 +01:00
b77fc0d32a Bump activity-ktx from 1.2.1 to 1.2.2 (#1249) 2021-03-25 11:04:37 +01:00
bd3716609e Bump hianalytics from 5.2.0.300 to 5.2.0.301 (#1250) 2021-03-25 11:04:04 +01:00
5b87cc9009 Bump agcp from 1.5.0.300 to 1.5.1.200 (#1251) 2021-03-25 11:03:37 +01:00
4f7be8d2cb Bump lifecycle-livedata-ktx from 2.3.0 to 2.3.1 (#1252) 2021-03-25 11:02:39 +01:00
8733e7782f Fix colorPrimary and class name in widget account manager (#1241) 2021-03-21 22:37:34 +01:00
e03b0dfa01 Fix missing avatars in widgets (#1238) 2021-03-21 12:04:55 +01:00
efafd2094a Add dialog with info about dropping support for android 4 (#1221) 2021-03-20 14:01:17 +01:00
1560335749 Fix very rare crash in login recovery (#1231) 2021-03-20 13:27:47 +01:00
5bee155f1e Remove listenablefuture from dependencies (#1237) 2021-03-20 12:10:53 +00:00
c9dc9a323f Bump gradle from 4.1.2 to 4.1.3 (#1234) 2021-03-19 15:02:28 +00:00
87e7e00705 Remove firebase inappmessage dependency (#1235) 2021-03-19 15:57:12 +01:00
57681b35ea Bump mockk from 1.10.6 to 1.11.0 (#1229) 2021-03-17 22:58:17 +00:00
8fb09d7b7d Merge branch 'release/1.1.2' into develop 2021-03-16 12:59:00 +01:00
168f750863 Merge branch 'release/1.1.2' 2021-03-16 12:58:56 +01:00
3e1acbd3bf Version 1.1.2 2021-03-16 12:58:52 +01:00
21ef2adcf6 Disable optimization in r8 config and fix crash in grade fragment (#1226) 2021-03-16 11:49:17 +00:00
3f6159e976 Fix show error details button in additional lessons (#1225) 2021-03-16 12:47:51 +01:00
555b5ec112 Merge branch 'release/1.1.1' into develop 2021-03-16 00:43:39 +01:00
60a9bcae46 Merge branch 'release/1.1.1' 2021-03-16 00:43:33 +01:00
eeb1341c1f Version 1.1.1 2021-03-16 00:43:28 +01:00
c77b50d51b New Crowdin updates (#1204) 2021-03-16 00:30:28 +01:00
8644ce32d5 Fix semester switch when student have only one semester (#1215) 2021-03-15 23:58:50 +01:00
94506aca52 Add github actions config to deploy apk to App Center (#1220) 2021-03-15 18:18:08 +01:00
eee4e1f4b5 Fix empty view in attendance (#1217) 2021-03-15 00:33:40 +01:00
c1942d012f Maybe fix fragment commits after activity state is saved (#1216) 2021-03-13 20:15:12 +01:00
fe846b463a Update material chips input (#1214) 2021-03-13 20:14:36 +01:00
0ea2e68249 Fix and clean proguard/r8 file (#1213) 2021-03-13 20:14:10 +01:00
be0445b227 Change the absence request confirmation message string (#1212) 2021-03-13 20:13:57 +01:00
48249f3093 Update kotlin coroutines (#1211) 2021-03-13 20:13:48 +01:00
8d7110735d Ignore no current student during avatar loading (#1210) 2021-03-13 20:13:37 +01:00
94957850c3 Bump fragment-ktx from 1.3.0 to 1.3.1 (#1205) 2021-03-12 21:50:59 +00:00
fa2cfc8427 Bump room from 2.3.0-beta02 to 2.3.0-beta03 (#1207) 2021-03-12 21:33:30 +00:00
3d467c43ba Use new kotlin compiler backend (#1202) 2021-03-12 22:07:27 +01:00
b76032044d Bump activity-ktx from 1.2.0 to 1.2.1 (#1206) 2021-03-12 21:06:33 +00:00
495b84204c Bump work_hilt from 1.0.0-alpha03 to 1.0.0-beta01 (#1208) 2021-03-12 21:06:27 +00:00
ea4b299de6 Bump firebase-bom from 26.6.0 to 26.7.0 (#1209) 2021-03-12 12:39:01 +00:00
acb5e2afd4 Replace dash mark with no data string in SchoolFragment (#1203) 2021-03-09 17:49:24 +01:00
50863d6ac2 Merge branch 'release/1.1.0' into develop 2021-03-07 21:58:30 +01:00
56 changed files with 774 additions and 262 deletions

75
.github/workflows/deploy-store.yml vendored Normal file
View File

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

144
.github/workflows/deploy-test.yml vendored Normal file
View File

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

View File

@ -1,13 +1,11 @@
name: Test and deploy name: Tests
on: on:
push: push:
branches: [ develop ] branches: [ master, develop ]
tags: [ '*' ] tags: [ '*' ]
pull_request: pull_request:
branches: [ develop ] branches: [ master, develop ]
workflow_dispatch:
jobs: jobs:
unit-tests: unit-tests:
@ -34,37 +32,3 @@ jobs:
- uses: codecov/codecov-action@v1 - uses: codecov/codecov-action@v1
with: with:
flags: unit flags: unit
deploy-google-play:
name: Deploy to google play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
needs: [ unit-tests ]
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Decrypt keys
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Upload apk to google play
env:
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;

View File

@ -32,7 +32,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
* powiadomienia np. o nowej ocenie * powiadomienia np. o nowej ocenie
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia * obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw * ciemny i czarny (AMOLED) motyw
* tryb offilne * tryb offline
* brak reklam * brak reklam
## Pobierz ## Pobierz

BIN
app/bitrise.jks.gpg Normal file

Binary file not shown.

View File

@ -4,7 +4,10 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.github.triplet.play' apply plugin: 'com.github.triplet.play'
apply plugin: 'ru.cian.huawei-publish'
apply plugin: 'com.mikepenz.aboutlibraries.plugin' apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect'
apply from: 'jacoco.gradle' apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle' apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle' apply from: 'hooks.gradle'
@ -18,8 +21,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 30 targetSdkVersion 30
versionCode 86 versionCode 91
versionName "1.1.0" versionName "1.1.5"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -115,6 +118,7 @@ android {
} }
kotlinOptions { kotlinOptions {
useIR = true
jvmTarget = "1.8" jvmTarget = "1.8"
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
} }
@ -134,32 +138,43 @@ play {
serviceAccountCredentials = file('key.p12') serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
updatePriority = 3 updatePriority = 1
}
huaweiPublish {
instances {
hmsRelease {
clientId = System.getenv("AGC_CLIENT_ID")
clientSecret = System.getenv("AGC_CLIENT_SECRET")
buildFormat = "apk"
deployType = "draft"
}
}
} }
ext { ext {
work_manager = "2.5.0" work_manager = "2.5.0"
work_hilt = "1.0.0-alpha03" work_hilt = "1.0.0-beta01"
room = "2.3.0-beta02" room = "2.3.0-rc01"
chucker = "3.4.0" chucker = "3.4.0"
mockk = "1.10.6" mockk = "1.11.0"
moshi = "1.11.0" moshi = "1.11.0"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.1.0" implementation "io.github.wulkanowy:sdk:1.1.5"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.2.0" implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat-resources:1.2.0" implementation "androidx.appcompat:appcompat-resources:1.2.0"
implementation "androidx.fragment:fragment-ktx:1.3.0" implementation "androidx.fragment:fragment-ktx:1.3.2"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.2.0"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
@ -169,14 +184,14 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.3.0" implementation "com.google.android.material:material:1.3.0"
implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.wulkanowy:material-chips-input:2.2.0"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.mikhaellopez:circularimageview:4.2.0' implementation 'com.mikhaellopez:circularimageview:4.2.0'
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -203,17 +218,14 @@ dependencies {
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation platform('com.google.firebase:firebase-bom:26.6.0') playImplementation platform('com.google.firebase:firebase-bom:26.7.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx"
playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.300' hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.1.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -239,6 +251,3 @@ dependencies {
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect'

View File

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

View File

@ -9,6 +9,6 @@ import javax.inject.Singleton
@Dao @Dao
interface RecipientDao : BaseDao<Recipient> { interface RecipientDao : BaseDao<Recipient> {
@Query("SELECT * FROM Recipients WHERE student_id = :userLoginId AND unit_id = :unitId AND role = :role") @Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role")
suspend fun loadAll(userLoginId: Int, unitId: Int, role: Int): List<Recipient> suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient>
} }

View File

@ -9,7 +9,7 @@ import java.io.Serializable
data class Recipient( data class Recipient(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
val userLoginId: Int, val studentId: Int,
@ColumnInfo(name = "real_id") @ColumnInfo(name = "real_id")
val realId: String, val realId: String,

View File

@ -5,7 +5,7 @@ import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map { fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map {
Recipient( Recipient(
userLoginId = userLoginId, studentId = userLoginId,
realId = it.id, realId = it.id,
realName = it.name, realName = it.name,
name = it.shortName, name = it.shortName,

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map { fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
ReportingUnit( ReportingUnit(
studentId = student.studentId, studentId = student.id.toInt(),
unitId = it.id, unitId = it.id,
roles = it.roles, roles = it.roles,
senderId = it.senderId, senderId = it.senderId,

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
@ -145,6 +146,10 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_subjects_without_grades R.bool.pref_default_subjects_without_grades
) )
var isKitkatDialogDisabled: Boolean
get() = sharedPref.getBoolean("kitkat_dialog_disabled", false)
set(value) = sharedPref.edit { putBoolean("kitkat_dialog_disabled", value) }
private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = private fun getString(id: String, default: Int) =

View File

@ -19,22 +19,22 @@ class RecipientRepository @Inject constructor(
) { ) {
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) { suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.senderId) val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
val old = recipientDb.loadAll(unit.senderId, unit.unitId, role) val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.deleteAll(old uniqueSubtract new)
recipientDb.insertAll(new uniqueSubtract old) recipientDb.insertAll(new uniqueSubtract old)
} }
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> { suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
return recipientDb.loadAll(unit.senderId, unit.unitId, role).ifEmpty { return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
refreshRecipients(student, unit, role) refreshRecipients(student, unit, role)
recipientDb.loadAll(unit.senderId, unit.unitId, role) recipientDb.loadAll(unit.studentId, unit.unitId, role)
} }
} }
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> { suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.userLoginId) return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
} }
} }

View File

@ -18,25 +18,25 @@ class ReportingUnitRepository @Inject constructor(
suspend fun refreshReportingUnits(student: Student) { suspend fun refreshReportingUnits(student: Student) {
val new = sdk.init(student).getReportingUnits().mapToEntities(student) val new = sdk.init(student).getReportingUnits().mapToEntities(student)
val old = reportingUnitDb.load(student.studentId) val old = reportingUnitDb.load(student.id.toInt())
reportingUnitDb.deleteAll(old.uniqueSubtract(new)) reportingUnitDb.deleteAll(old.uniqueSubtract(new))
reportingUnitDb.insertAll(new.uniqueSubtract(old)) reportingUnitDb.insertAll(new.uniqueSubtract(old))
} }
suspend fun getReportingUnits(student: Student): List<ReportingUnit> { suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
return reportingUnitDb.load(student.studentId).ifEmpty { return reportingUnitDb.load(student.id.toInt()).ifEmpty {
refreshReportingUnits(student) refreshReportingUnits(student)
reportingUnitDb.load(student.studentId) reportingUnitDb.load(student.id.toInt())
} }
} }
suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? { suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? {
return reportingUnitDb.loadOne(student.studentId, unitId) ?: run { return reportingUnitDb.loadOne(student.id.toInt(), unitId) ?: run {
refreshReportingUnits(student) refreshReportingUnits(student)
return reportingUnitDb.loadOne(student.studentId, unitId) return reportingUnitDb.loadOne(student.id.toInt(), unitId)
} }
} }
} }

View File

@ -1,20 +1,25 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject import javax.inject.Inject
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() { class WidgetConfigureAdapter @Inject constructor() :
RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
var items = emptyList<Pair<Student, Boolean>>() var items = emptyList<StudentWithSemesters>()
var selectedId = -1L
var onClickListener: (Student) -> Unit = {} var onClickListener: (Student) -> Unit = {}
@ -26,17 +31,33 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<Widget
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (student, isCurrent) = items[position] val (student, semesters) = items[position]
val semester = semesters.maxByOrNull { it.semesterId }
val context = holder.binding.root.context
val checkBackgroundColor = context.getThemeAttrColor(R.attr.colorSurface)
val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor)
val isDuplicatedStudent = items.filter {
val studentToCompare = it.student
studentToCompare.studentId == student.studentId
&& studentToCompare.schoolSymbol == student.schoolSymbol
&& studentToCompare.symbol == student.symbol
}.size > 1
with(holder.binding) { with(holder.binding) {
accountItemName.text = "${student.nickOrName} ${student.className}" accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}"
accountItemSchool.text = student.schoolName accountItemSchool.text = student.schoolName
accountItemImage.setImageDrawable(avatar)
with(accountItemImage) { with(accountItemAccountType) {
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
else context.getThemeAttrColor(R.attr.colorOnSurface, 153) isVisible = isDuplicatedStudent
}
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN) with(accountItemCheck) {
isVisible = student.id == selectedId
borderColor = checkBackgroundColor
circleColor = checkBackgroundColor
} }
root.setOnClickListener { onClickListener(student) } root.setOnClickListener { onClickListener(student) }

View File

@ -74,9 +74,8 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
studentWithSemesters: StudentWithSemesters studentWithSemesters: StudentWithSemesters
) { ) {
val context = binding.root.context val context = binding.root.context
val student = studentWithSemesters.student val (student, semesters) = studentWithSemesters
val semesters = studentWithSemesters.semesters val semester = semesters.maxByOrNull { it.semesterId }
val diary = semesters.maxByOrNull { it.semesterId }
val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor) val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor)
val checkBackgroundColor = val checkBackgroundColor =
context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface) context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface)
@ -90,7 +89,7 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
}.size > 1 && isAccountQuickDialogMode }.size > 1 && isAccountQuickDialogMode
with(binding) { with(binding) {
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}" accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}"
accountItemSchool.text = studentWithSemesters.student.schoolName accountItemSchool.text = studentWithSemesters.student.schoolName
accountItemImage.setImageDrawable(avatar) accountItemImage.setImageDrawable(avatar)

View File

@ -192,7 +192,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
binding. attendanceRecycler.visibility = if (show) VISIBLE else GONE binding.attendanceRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun showRefresh(show: Boolean) { override fun showRefresh(show: Boolean) {

View File

@ -190,35 +190,48 @@ class AttendancePresenter @Inject constructor(
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
attendanceRepository.getAttendance(student, semester, currentDate, currentDate, forceRefresh) attendanceRepository.getAttendance(
student,
semester,
currentDate,
currentDate,
forceRefresh
)
}.onEach { }.onEach {
when (it.status) { when (it.status) {
Status.LOADING -> { Status.LOADING -> {
view?.showExcuseButton(false) view?.showExcuseButton(false)
if (!it.data.isNullOrEmpty()) { if (!it.data.isNullOrEmpty()) {
val filteredAttendance = if (prefRepository.isShowPresent) {
it.data
} else {
it.data.filter { item -> !item.presence }
}
view?.run { view?.run {
enableSwipe(true) enableSwipe(true)
showRefresh(true) showRefresh(true)
showProgress(false) showProgress(false)
showContent(true) showEmpty(filteredAttendance.isEmpty())
updateData(it.data.let { items -> showContent(filteredAttendance.isNotEmpty())
if (prefRepository.isShowPresent) items updateData(filteredAttendance.sortedBy { item -> item.number })
else items.filter { item -> !item.presence }
}.sortedBy { item -> item.number })
} }
} }
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading attendance result: Success") Timber.i("Loading attendance result: Success")
val filteredAttendance = if (prefRepository.isShowPresent) {
it.data.orEmpty()
} else {
it.data?.filter { item -> !item.presence }.orEmpty()
}
view?.apply { view?.apply {
updateData(it.data!!.let { items -> updateData(filteredAttendance.sortedBy { item -> item.number })
if (prefRepository.isShowPresent) items showEmpty(filteredAttendance.isEmpty())
else items.filter { item -> !item.presence }
}.sortedBy { item -> item.number })
showEmpty(it.data.isEmpty())
showErrorView(false) showErrorView(false)
showContent(it.data.isNotEmpty()) showContent(filteredAttendance.isNotEmpty())
showExcuseButton(it.data.any { item -> item.excusable }) showExcuseButton(filteredAttendance.any { item -> item.excusable })
} }
analytics.logEvent( analytics.logEvent(
"load_data", "load_data",

View File

@ -68,7 +68,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
averageMode: GradeAverageMode averageMode: GradeAverageMode
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = val firstSemester =
semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
@ -78,8 +78,7 @@ class GradeAverageProvider @Inject constructor(
if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects
val firstSemesterGradeSubjects = val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh)
getGradeSubjects(student, firstSemester, forceRefresh)
return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
if (firstSemesterGradeSubject.status == Status.ERROR) { if (firstSemesterGradeSubject.status == Status.ERROR) {
@ -87,9 +86,9 @@ class GradeAverageProvider @Inject constructor(
} }
val isAnyVulcanAverageInFirstSemester = val isAnyVulcanAverageInFirstSemester =
firstSemesterGradeSubject.data.orEmpty().any { it.average != .0 } firstSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage }
val isAnyVulcanAverageInSecondSemester = val isAnyVulcanAverageInSecondSemester =
secondSemesterGradeSubject.data.orEmpty().any { it.average != .0 } secondSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage }
val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject -> val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject ->
val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty() val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty()
@ -99,7 +98,7 @@ class GradeAverageProvider @Inject constructor(
calculateAllYearAverage( calculateAllYearAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
gradeAverageForceCalc = gradeAverageForceCalc, isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject firstSemesterSubject = firstSemesterSubject
) )
@ -107,7 +106,7 @@ class GradeAverageProvider @Inject constructor(
calculateBothSemestersAverage( calculateBothSemestersAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
gradeAverageForceCalc = gradeAverageForceCalc, isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject firstSemesterSubject = firstSemesterSubject
) )
@ -121,10 +120,10 @@ class GradeAverageProvider @Inject constructor(
private fun calculateAllYearAverage( private fun calculateAllYearAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
gradeAverageForceCalc: Boolean, isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject? firstSemesterSubject: GradeSubject?
) = if (!isAnyVulcanAverage || gradeAverageForceCalc) { ) = if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
val updatedSecondSemesterGrades = val updatedSecondSemesterGrades =
secondSemesterSubject.grades.updateModifiers(student) secondSemesterSubject.grades.updateModifiers(student)
val updatedFirstSemesterGrades = val updatedFirstSemesterGrades =
@ -138,13 +137,13 @@ class GradeAverageProvider @Inject constructor(
private fun calculateBothSemestersAverage( private fun calculateBothSemestersAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
gradeAverageForceCalc: Boolean, isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject? firstSemesterSubject: GradeSubject?
): Double { ): Double {
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
return if (!isAnyVulcanAverage || gradeAverageForceCalc) { return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
val secondSemesterAverage = val secondSemesterAverage =
secondSemesterSubject.grades.updateModifiers(student).calcAverage() secondSemesterSubject.grades.updateModifiers(student).calcAverage()
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student) val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
@ -161,7 +160,7 @@ class GradeAverageProvider @Inject constructor(
semester: Semester, semester: Semester,
forceRefresh: Boolean forceRefresh: Boolean
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.map { res -> .map { res ->
@ -170,20 +169,21 @@ class GradeAverageProvider @Inject constructor(
val allGrades = details.orEmpty().groupBy { it.subject } val allGrades = details.orEmpty().groupBy { it.subject }
val items = summaries?.emulateEmptySummaries( val items = summaries?.emulateEmptySummaries(
student, student = student,
semester, semester = semester,
allGrades.toList(), grades = allGrades.toList(),
isAnyAverage calcAverage = isAnyAverage
)?.map { summary -> )?.map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
GradeSubject( GradeSubject(
subject = summary.subject, subject = summary.subject,
average = if (!isAnyAverage || gradeAverageForceCalc) { average = if (!isAnyAverage || isGradeAverageForceCalc) {
grades.updateModifiers(student).calcAverage() grades.updateModifiers(student).calcAverage()
} else summary.average, } else summary.average,
points = summary.pointsSum, points = summary.pointsSum,
summary = summary, summary = summary,
grades = grades grades = grades,
isVulcanAverage = isAnyAverage
) )
} }

View File

@ -10,6 +10,7 @@ import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.databinding.FragmentGradeBinding import io.github.wulkanowy.databinding.FragmentGradeBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
@ -121,11 +122,9 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
semesterSwitchMenu?.isVisible = show semesterSwitchMenu?.isVisible = show
} }
override fun showSemesterDialog(selectedIndex: Int) { override fun showSemesterDialog(selectedIndex: Int, semesters: List<Semester>) {
val choices = arrayOf( val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) }
getString(R.string.grade_semester, 1), .toTypedArray()
getString(R.string.grade_semester, 2)
)
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setSingleChoiceItems(choices, selectedIndex) { dialog, which -> .setSingleChoiceItems(choices, selectedIndex) { dialog, which ->

View File

@ -49,7 +49,9 @@ class GradePresenter @Inject constructor(
} }
fun onSemesterSwitch(): Boolean { fun onSemesterSwitch(): Boolean {
if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex - 1) if (semesters.isNotEmpty()) {
view?.showSemesterDialog(selectedIndex - 1, semesters.take(2))
}
return true return true
} }
@ -137,11 +139,17 @@ class GradePresenter @Inject constructor(
private fun loadChild(index: Int, forceRefresh: Boolean = false) { private fun loadChild(index: Int, forceRefresh: Boolean = false) {
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}") Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}")
semesters.first { it.semesterName == selectedIndex }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) { val newSelectedSemesterId = try {
Timber.i("Load grade child view index: $index") semesters.first { it.semesterName == selectedIndex }.semesterId
view?.notifyChildLoadData(index, it, forceRefresh) } catch (e: NoSuchElementException) {
} Timber.e(e, "Selected semester no exists")
return
}
if (forceRefresh || loadedSemesterId[index] != newSelectedSemesterId) {
Timber.i("Load grade child view index: $index")
view?.notifyChildLoadData(index, newSelectedSemesterId, forceRefresh)
} }
} }

View File

@ -8,5 +8,6 @@ data class GradeSubject(
val average: Double, val average: Double,
val points: String, val points: String,
val summary: GradeSummary, val summary: GradeSummary,
val grades: List<Grade> val grades: List<Grade>,
val isVulcanAverage: Boolean
) )

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface GradeView : BaseView { interface GradeView : BaseView {
@ -18,7 +19,7 @@ interface GradeView : BaseView {
fun showSemesterSwitch(show: Boolean) fun showSemesterSwitch(show: Boolean)
fun showSemesterDialog(selectedIndex: Int) fun showSemesterDialog(selectedIndex: Int, semesters: List<Semester>)
fun setCurrentSemesterName(semester: Int, schoolYear: Int) fun setCurrentSemesterName(semester: Int, schoolYear: Int)

View File

@ -162,7 +162,7 @@ class LoginFormPresenter @Inject constructor(
view?.setErrorEmailRequired() view?.setErrorEmailRequired()
isCorrect = false isCorrect = false
} }
if ("@" in login && "login" !in host && "email" !in host) { if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
val emailHost = login.substringAfter("@") val emailHost = login.substringAfter("@")
val emailDomain = URL(host).host val emailDomain = URL(host).host
if (emailHost != emailDomain) { if (emailHost != emailDomain) {

View File

@ -9,6 +9,7 @@ import android.view.View.VISIBLE
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -42,10 +43,12 @@ class LoginRecoverFragment :
private lateinit var hostSymbols: Array<String> private lateinit var hostSymbols: Array<String>
override val recoverHostValue: String override val recoverHostValue: String
get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString()))
.orEmpty()
override val formHostSymbol: String override val formHostSymbol: String
get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty() get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString()))
.orEmpty()
override val recoverNameValue: String override val recoverNameValue: String
get() = bindingLocal.loginRecoverName.text.toString().trim() get() = bindingLocal.loginRecoverName.text.toString().trim()
@ -82,7 +85,9 @@ class LoginRecoverFragment :
with(bindingLocal.loginRecoverHost) { with(bindingLocal.loginRecoverHost) {
setText(hostKeys.getOrNull(0).orEmpty()) setText(hostKeys.getOrNull(0).orEmpty())
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)) setAdapter(
LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys)
)
setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() } setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() }
} }
} }
@ -127,6 +132,7 @@ class LoginRecoverFragment :
override fun showErrorView(show: Boolean) { override fun showErrorView(show: Boolean) {
bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE
bindingLocal.loginRecoverErrorDetails.isVisible = true
} }
override fun setErrorDetails(message: String) { override fun setErrorDetails(message: String) {
@ -166,7 +172,7 @@ class LoginRecoverFragment :
with(bindingLocal.loginRecoverWebView) { with(bindingLocal.loginRecoverWebView) {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
webViewClient = object : WebViewClient() { webViewClient = object : WebViewClient() {
private var recoverWebViewSuccess: Boolean = true private var recoverWebViewSuccess = true
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(view: WebView?, url: String?) {
if (recoverWebViewSuccess) { if (recoverWebViewSuccess) {
@ -175,10 +181,16 @@ class LoginRecoverFragment :
} else { } else {
showProgress(false) showProgress(false)
showErrorView(true) showErrorView(true)
bindingLocal.loginRecoverErrorDetails.isVisible = false
} }
} }
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { override fun onReceivedError(
view: WebView,
errorCode: Int,
description: String,
failingUrl: String
) {
recoverWebViewSuccess = false recoverWebViewSuccess = false
} }
} }

View File

@ -11,7 +11,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.base.WidgetConfigureAdapter
@ -38,7 +38,9 @@ class LuckyNumberWidgetConfigureActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
setContentView(ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root) setContentView(
ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root
)
intent.extras.let { intent.extras.let {
presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID))
@ -70,8 +72,9 @@ class LuckyNumberWidgetConfigureActivity :
.show() .show()
} }
override fun updateData(data: List<Pair<Student, Boolean>>) { override fun updateData(data: List<StudentWithSemesters>, selectedStudentId: Long) {
with(configureAdapter) { with(configureAdapter) {
selectedId = selectedStudentId
items = data items = data
notifyDataSetChanged() notifyDataSetChanged()
} }

View File

@ -51,16 +51,17 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
when (it.status) { when (it.status) {
Status.LOADING -> Timber.d("Lucky number widget configure students data load") Status.LOADING -> Timber.d("Lucky number widget configure students data load")
Status.SUCCESS -> { Status.SUCCESS -> {
val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } val selectedStudentId = appWidgetId?.let { id ->
sharedPref.getLong(getStudentWidgetKey(id), 0)
} ?: -1
when { when {
it.data!!.isEmpty() -> view?.openLoginView() it.data!!.isEmpty() -> view?.openLoginView()
it.data.size == 1 -> { it.data.size == 1 -> {
selectedStudent = it.data.single().student selectedStudent = it.data.single().student
view?.showThemeDialog() view?.showThemeDialog()
} }
else -> view?.updateData(it.data.map { entity -> else -> view?.updateData(it.data, selectedStudentId)
entity.student to (entity.student.id == widgetId)
})
} }
} }
Status.ERROR -> errorHandler.dispatch(it.error!!) Status.ERROR -> errorHandler.dispatch(it.error!!)

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget package io.github.wulkanowy.ui.modules.luckynumberwidget
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LuckyNumberWidgetConfigureView : BaseView { interface LuckyNumberWidgetConfigureView : BaseView {
@ -9,7 +9,7 @@ interface LuckyNumberWidgetConfigureView : BaseView {
fun showThemeDialog() fun showThemeDialog()
fun updateData(data: List<Pair<Student, Boolean>>) fun updateData(data: List<StudentWithSemesters>, selectedStudentId: Long)
fun updateLuckyNumberWidget(widgetId: Int) fun updateLuckyNumberWidget(widgetId: Int)

View File

@ -291,7 +291,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
): Boolean { ): Boolean {
val fragment = val fragment =
supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment) supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
navController.pushFragment(fragment) pushView(fragment)
return true return true
} }
@ -305,6 +305,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
} }
override fun switchMenuView(position: Int) { override fun switchMenuView(position: Int) {
if (supportFragmentManager.isStateSaved) return
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.switchTab(position) navController.switchTab(position)
} }
@ -322,7 +324,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
} }
override fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>) { override fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>) {
navController.showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters)) showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters))
} }
override fun showActionBarElevation(show: Boolean) { override fun showActionBarElevation(show: Boolean) {
@ -338,16 +340,31 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentChanged() (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentChanged()
} }
@Suppress("DEPRECATION")
fun showDialogFragment(dialog: DialogFragment) { fun showDialogFragment(dialog: DialogFragment) {
if (supportFragmentManager.isStateSaved) return
//Deprecated method is used here to avoid fragnav bug
if (navController.currentDialogFrag?.fragmentManager == null) {
FragNavController::class.java.getDeclaredField("mCurrentDialogFrag").apply {
isAccessible = true
set(navController, null)
}
}
navController.showDialogFragment(dialog) navController.showDialogFragment(dialog)
} }
fun pushView(fragment: Fragment) { fun pushView(fragment: Fragment) {
if (supportFragmentManager.isStateSaved) return
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.pushFragment(fragment) navController.pushFragment(fragment)
} }
override fun popView(depth: Int) { override fun popView(depth: Int) {
if (supportFragmentManager.isStateSaved) return
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.safelyPopFragments(depth) navController.safelyPopFragments(depth)
} }

View File

@ -113,7 +113,7 @@ class MainPresenter @Inject constructor(
private fun showCurrentStudentAvatar() { private fun showCurrentStudentAvatar() {
val currentStudent = val currentStudent =
studentsWitSemesters!!.single { it.student.isCurrent }.student studentsWitSemesters?.singleOrNull { it.student.isCurrent }?.student ?: return
view?.showStudentAvatar(currentStudent) view?.showStudentAvatar(currentStudent)
} }

View File

@ -53,13 +53,14 @@ class SchoolFragment : BaseFragment<FragmentSchoolBinding>(R.layout.fragment_sch
override fun updateData(data: School) { override fun updateData(data: School) {
with(binding) { with(binding) {
schoolName.text = data.name val noDataString = getString(R.string.all_no_data)
schoolAddress.text = data.address.ifBlank { "-" } schoolName.text = data.name.ifBlank { noDataString }
schoolAddress.text = data.address.ifBlank { noDataString }
schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE
schoolTelephone.text = data.contact.ifBlank { "-" } schoolTelephone.text = data.contact.ifBlank { noDataString }
schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE
schoolHeadmaster.text = data.headmaster schoolHeadmaster.text = data.headmaster.ifBlank { noDataString }
schoolPedagogue.text = data.pedagogue schoolPedagogue.text = data.pedagogue.ifBlank { noDataString }
} }
} }

View File

@ -3,17 +3,23 @@ package io.github.wulkanowy.ui.modules.splash
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView { class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView {
@Inject
lateinit var appInfo: AppInfo
@Inject @Inject
override lateinit var presenter: SplashPresenter override lateinit var presenter: SplashPresenter
@ -40,4 +46,14 @@ class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
Toast.makeText(this, text, LENGTH_LONG).show() Toast.makeText(this, text, LENGTH_LONG).show()
} }
override fun showKitkatView() {
AlertDialog.Builder(this)
.setTitle(R.string.drop_kitkat_title)
.setMessage(R.string.drop_kitkat_content)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.drop_kitkat_again) { _, _ -> presenter.onNeutralButtonSelected() }
.setOnDismissListener { presenter.onKitkatViewDismissed() }
.show()
}
} }

View File

@ -1,9 +1,12 @@
package io.github.wulkanowy.ui.modules.splash package io.github.wulkanowy.ui.modules.splash
import android.os.Build
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -11,25 +14,47 @@ import javax.inject.Inject
class SplashPresenter @Inject constructor( class SplashPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository,
private val appInfo: AppInfo
) : BasePresenter<SplashView>(errorHandler, studentRepository) { ) : BasePresenter<SplashView>(errorHandler, studentRepository) {
private var externalUrl: String? = null
fun onAttachView(view: SplashView, externalUrl: String?) { fun onAttachView(view: SplashView, externalUrl: String?) {
super.onAttachView(view) super.onAttachView(view)
this.externalUrl = externalUrl
if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP && !preferencesRepository.isKitkatDialogDisabled) {
view.showKitkatView()
} else {
loadCorrectDataOrUser()
}
}
private fun loadCorrectDataOrUser() {
if (!externalUrl.isNullOrBlank()) { if (!externalUrl.isNullOrBlank()) {
return view.openExternalUrlAndFinish(externalUrl) view?.openExternalUrlAndFinish(externalUrl!!)
return
} }
flowWithResource { studentRepository.isCurrentStudentSet() }.onEach { flowWithResource { studentRepository.isCurrentStudentSet() }.onEach {
when (it.status) { when (it.status) {
Status.LOADING -> Timber.d("Is current user set check started") Status.LOADING -> Timber.d("Is current user set check started")
Status.SUCCESS -> with(view) { Status.SUCCESS -> {
if (it.data!!) openMainView() if (it.data!!) view?.openMainView()
else openLoginView() else view?.openLoginView()
} }
Status.ERROR -> errorHandler.dispatch(it.error!!) Status.ERROR -> errorHandler.dispatch(it.error!!)
} }
}.launch() }.launch()
} }
fun onKitkatViewDismissed() {
loadCorrectDataOrUser()
}
fun onNeutralButtonSelected() {
preferencesRepository.isKitkatDialogDisabled = true
}
} }

View File

@ -9,4 +9,6 @@ interface SplashView : BaseView {
fun openMainView() fun openMainView()
fun openExternalUrlAndFinish(url: String) fun openExternalUrlAndFinish(url: String)
fun showKitkatView()
} }

View File

@ -57,6 +57,7 @@ class AdditionalLessonsFragment :
additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
additionalLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) additionalLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() } additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() }
additionalLessonsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() } additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() }

View File

@ -80,6 +80,10 @@ class AdditionalLessonsPresenter @Inject constructor(
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun setBaseDateOnHolidays() { private fun setBaseDateOnHolidays() {
flow { flow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()

View File

@ -12,7 +12,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.WidgetConfigureAdapter import io.github.wulkanowy.ui.base.WidgetConfigureAdapter
@ -37,13 +37,19 @@ class TimetableWidgetConfigureActivity :
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
override public fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
setContentView(ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root) setContentView(
ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root
)
intent.extras.let { intent.extras.let {
presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID), it?.getBoolean(EXTRA_FROM_PROVIDER)) presenter.onAttachView(
this,
it?.getInt(EXTRA_APPWIDGET_ID),
it?.getBoolean(EXTRA_FROM_PROVIDER)
)
} }
} }
@ -61,6 +67,7 @@ class TimetableWidgetConfigureActivity :
getString(R.string.widget_timetable_theme_light), getString(R.string.widget_timetable_theme_light),
getString(R.string.widget_timetable_theme_dark) getString(R.string.widget_timetable_theme_dark)
) )
if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system) if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system)
dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher)
@ -72,8 +79,9 @@ class TimetableWidgetConfigureActivity :
.show() .show()
} }
override fun updateData(data: List<Pair<Student, Boolean>>) { override fun updateData(data: List<StudentWithSemesters>, selectedStudentId: Long) {
with(configureAdapter) { with(configureAdapter) {
selectedId = selectedStudentId
items = data items = data
notifyDataSetChanged() notifyDataSetChanged()
} }

View File

@ -25,7 +25,11 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
private var selectedStudent: Student? = null private var selectedStudent: Student? = null
fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) { fun onAttachView(
view: TimetableWidgetConfigureView,
appWidgetId: Int?,
isFromProvider: Boolean?
) {
super.onAttachView(view) super.onAttachView(view)
this.appWidgetId = appWidgetId this.appWidgetId = appWidgetId
this.isFromProvider = isFromProvider ?: false this.isFromProvider = isFromProvider ?: false
@ -56,16 +60,17 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
when (it.status) { when (it.status) {
Status.LOADING -> Timber.d("Timetable widget configure students data load") Status.LOADING -> Timber.d("Timetable widget configure students data load")
Status.SUCCESS -> { Status.SUCCESS -> {
val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } val selectedStudentId = appWidgetId?.let { id ->
sharedPref.getLong(getStudentWidgetKey(id), 0)
} ?: -1
when { when {
it.data!!.isEmpty() -> view?.openLoginView() it.data!!.isEmpty() -> view?.openLoginView()
it.data.size == 1 && !isFromProvider -> { it.data.size == 1 && !isFromProvider -> {
selectedStudent = it.data.single().student selectedStudent = it.data.single().student
view?.showThemeDialog() view?.showThemeDialog()
} }
else -> view?.updateData(it.data.map { entity -> else -> view?.updateData(it.data, selectedStudentId)
entity.student to (entity.student.id == widgetId)
})
} }
} }
Status.ERROR -> errorHandler.dispatch(it.error!!) Status.ERROR -> errorHandler.dispatch(it.error!!)

View File

@ -1,13 +1,13 @@
package io.github.wulkanowy.ui.modules.timetablewidget package io.github.wulkanowy.ui.modules.timetablewidget
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface TimetableWidgetConfigureView : BaseView { interface TimetableWidgetConfigureView : BaseView {
fun initView() fun initView()
fun updateData(data: List<Pair<Student, Boolean>>) fun updateData(data: List<StudentWithSemesters>, selectedStudentId: Long)
fun updateTimetableWidget(widgetId: Int) fun updateTimetableWidget(widgetId: Int)

View File

@ -13,6 +13,8 @@ import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -25,6 +27,8 @@ import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
@ -72,7 +76,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId"
fun getCurrentThemeWidgetKey(appWidgetId: Int) = "timetable_widget_current_theme_$appWidgetId" fun getCurrentThemeWidgetKey(appWidgetId: Int) =
"timetable_widget_current_theme_$appWidgetId"
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
@ -88,21 +93,29 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
private suspend fun onUpdate(context: Context, intent: Intent) { private suspend fun onUpdate(context: Context, intent: Intent) {
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) { if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) {
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId ->
val student = getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) val student =
getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student) updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student)
} }
} else { } else {
val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE)
val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0)
val student = getStudent(sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId) val student = getStudent(
val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0),
toggledWidgetId
)
val savedDate =
LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0))
val date = when (buttonType) { val date = when (buttonType) {
BUTTON_RESET -> now().nextOrSameSchoolDay BUTTON_RESET -> now().nextOrSameSchoolDay
BUTTON_NEXT -> savedDate.nextSchoolDay BUTTON_NEXT -> savedDate.nextSchoolDay
BUTTON_PREV -> savedDate.previousSchoolDay BUTTON_PREV -> savedDate.previousSchoolDay
else -> now().nextOrSameSchoolDay else -> now().nextOrSameSchoolDay
} }
if (!buttonType.isNullOrBlank()) analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) if (!buttonType.isNullOrBlank()) analytics.logEvent(
"changed_timetable_widget_day",
"button" to buttonType
)
updateWidget(context, toggledWidgetId, date, student) updateWidget(context, toggledWidgetId, date, student)
} }
} }
@ -121,9 +134,15 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { private fun updateWidget(
context: Context,
appWidgetId: Int,
date: LocalDate,
student: Student?
) {
val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES val isSystemDarkMode =
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
var currentTheme = 0L var currentTheme = 0L
var layoutId = R.layout.widget_timetable var layoutId = R.layout.widget_timetable
@ -134,21 +153,28 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT)
val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV)
val resetNavIntent = createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) val resetNavIntent =
createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET)
val adapterIntent = Intent(context, TimetableWidgetService::class.java) val adapterIntent = Intent(context, TimetableWidgetService::class.java)
.apply { .apply {
putExtra(EXTRA_APPWIDGET_ID, appWidgetId) putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
//make Intent unique //make Intent unique
action = appWidgetId.toString() action = appWidgetId.toString()
} }
val accountIntent = PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId, val accountIntent = PendingIntent.getActivity(
context, -Int.MAX_VALUE + appWidgetId,
Intent(context, TimetableWidgetConfigureActivity::class.java).apply { Intent(context, TimetableWidgetConfigureActivity::class.java).apply {
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
putExtra(EXTRA_APPWIDGET_ID, appWidgetId) putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
putExtra(EXTRA_FROM_PROVIDER, true) putExtra(EXTRA_FROM_PROVIDER, true)
}, FLAG_UPDATE_CURRENT) }, FLAG_UPDATE_CURRENT
val appIntent = PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id, )
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT) val appIntent = PendingIntent.getActivity(
context,
MainView.Section.TIMETABLE.id,
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
FLAG_UPDATE_CURRENT
)
val remoteView = RemoteViews(context.packageName, layoutId).apply { val remoteView = RemoteViews(context.packageName, layoutId).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
@ -160,6 +186,11 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
R.id.timetableWidgetName, R.id.timetableWidgetName,
student?.nickOrName ?: context.getString(R.string.all_no_data) student?.nickOrName ?: context.getString(R.string.all_no_data)
) )
student?.let {
setImageViewBitmap(R.id.timetableWidgetAccount, context.createAvatarBitmap(it))
}
setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
@ -181,13 +212,20 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
} }
} }
private fun createNavIntent(context: Context, code: Int, appWidgetId: Int, buttonType: String): PendingIntent { private fun createNavIntent(
return PendingIntent.getBroadcast(context, code, context: Context,
code: Int,
appWidgetId: Int,
buttonType: String
): PendingIntent {
return PendingIntent.getBroadcast(
context, code,
Intent(context, TimetableWidgetProvider::class.java).apply { Intent(context, TimetableWidgetProvider::class.java).apply {
action = ACTION_APPWIDGET_UPDATE action = ACTION_APPWIDGET_UPDATE
putExtra(EXTRA_BUTTON_TYPE, buttonType) putExtra(EXTRA_BUTTON_TYPE, buttonType)
putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId)
}, FLAG_UPDATE_CURRENT) }, FLAG_UPDATE_CURRENT
)
} }
private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
@ -208,4 +246,29 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
} }
null null
} }
private fun Context.createAvatarBitmap(student: Student): Bitmap {
val avatarColor = if (student.avatarColor == -2937041L) {
getCompatColor(R.color.colorPrimaryLight).toLong()
} else {
student.avatarColor
}
val avatarDrawable = createNameInitialsDrawable(student.nickOrName, avatarColor, 0.5f)
val avatarBitmap =
if (avatarDrawable.intrinsicWidth <= 0 || avatarDrawable.intrinsicHeight <= 0) {
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
} else {
Bitmap.createBitmap(
avatarDrawable.intrinsicWidth,
avatarDrawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
}
val canvas = Canvas(avatarBitmap)
avatarDrawable.setBounds(0, 0, canvas.width, canvas.height)
avatarDrawable.draw(canvas)
return avatarBitmap
}
} }

View File

@ -1,11 +1,5 @@
Wersja 1.1.0 Wersja 1.1.5
- dodaliśmy wyświetlanie inicjałów imienia ucznia jako awatar widoczny w aplikacji Naprawiliśmy liczenie średniej wszystkich ocen z całego roku
- dodaliśmy historię szczęśliwego numerka
- dodaliśmy język słowacki
- zmieniliśmy kolor górnego i dolnego paska systemowego lepiej dostosowując je do aplikacji
- zmieniliśmy wygląd ustawień dzieląc je na sekcje
- naprawiliśmy problem dublujących się czasem ocen
- naprawiliśmy kilka innych błędów i poprawiliśmy stabilność aplikacji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -25,8 +25,7 @@
android:id="@+id/loginRecoverFormContainer" android:id="@+id/loginRecoverFormContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="16dp" android:paddingBottom="16dp">
android:visibility="visible">
<TextView <TextView
android:id="@+id/loginFormHeader" android:id="@+id/loginFormHeader"
@ -85,9 +84,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autofillHints="emailAddress" android:autofillHints="emailAddress"
android:imeOptions="actionDone"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
android:maxLines="1" android:maxLines="1"
android:imeOptions="actionDone"
tools:targetApi="o" /> tools:targetApi="o" />
<requestFocus /> <requestFocus />
@ -156,7 +155,7 @@
android:orientation="vertical" android:orientation="vertical"
android:visibility="invisible" android:visibility="invisible"
tools:ignore="UseCompoundDrawables" tools:ignore="UseCompoundDrawables"
tools:visibility="invisible"> tools:visibility="visible">
<ImageView <ImageView
android:layout_width="100dp" android:layout_width="100dp"
@ -175,12 +174,10 @@
android:text="@string/error_unknown" android:text="@string/error_unknown"
android:textSize="20sp" /> android:textSize="20sp" />
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp">
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/loginRecoverErrorDetails" android:id="@+id/loginRecoverErrorDetails"
@ -189,14 +186,21 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:text="@string/all_details" /> android:text="@string/all_details"
app:layout_constraintEnd_toStartOf="@id/loginRecoverErrorRetry"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/loginRecoverErrorRetry" android:id="@+id/loginRecoverErrorRetry"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/all_retry" /> android:text="@string/all_retry"
</LinearLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/loginRecoverErrorDetails"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
@ -206,7 +210,7 @@
android:fillViewport="true" android:fillViewport="true"
android:scrollbars="none" android:scrollbars="none"
android:visibility="invisible" android:visibility="invisible"
tools:visibility="invisible"> tools:visibility="gone">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -253,6 +257,5 @@
android:layout_marginBottom="30dp" android:layout_marginBottom="30dp"
android:text="@android:string/ok" /> android:text="@android:string/ok" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</FrameLayout> </FrameLayout>

View File

@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Důvod nepřítomnosti (volitelný)</string> <string name="attendance_excuse_dialog_reason">Důvod nepřítomnosti (volitelný)</string>
<string name="attendance_excuse_dialog_submit">Poslat</string> <string name="attendance_excuse_dialog_submit">Poslat</string>
<string name="attendance_excuse_success">Nepřítomnost úspěšně omluvena!</string> <string name="attendance_excuse_success">Žádost o omluvu nepřítomnosti byla úspěšně odeslána!</string>
<string name="attendance_excuse_no_selection">Musíte vybrat alespoň jednu nepřítomnost!</string> <string name="attendance_excuse_no_selection">Musíte vybrat alespoň jednu nepřítomnost!</string>
<string name="attendance_excuse_title">Ospravedlnit</string> <string name="attendance_excuse_title">Ospravedlnit</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -506,6 +506,10 @@
<string name="channel_push">Push upozornění</string> <string name="channel_push">Push upozornění</string>
<string name="channel_upcoming_lessons">Nadcházející lekce</string> <string name="channel_upcoming_lessons">Nadcházející lekce</string>
<string name="channel_debug">Ladění</string> <string name="channel_debug">Ladění</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Konec podpory</string>
<string name="drop_kitkat_content">Ukončujeme podporu pro vaše zařízení. V Wulkanovým už pro něj nebudou žádné nové funkce. Kritické opravy však budeme vydávat až do konce roku 2021, abyste měli čas přejít na novější model</string>
<string name="drop_kitkat_again">Nezobrazovat znovu</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Černý</string> <string name="all_black">Černý</string>
<string name="all_red">Červený</string> <string name="all_red">Červený</string>

View File

@ -176,7 +176,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Abwesenheitsgrund (optional)</string> <string name="attendance_excuse_dialog_reason">Abwesenheitsgrund (optional)</string>
<string name="attendance_excuse_dialog_submit">Senden</string> <string name="attendance_excuse_dialog_submit">Senden</string>
<string name="attendance_excuse_success">Abwesenheit erfolgreich entschuldigt!</string> <string name="attendance_excuse_success">Abwesenheitsentschuldigungsanfrage erfolgreich gesendet!</string>
<string name="attendance_excuse_no_selection">Sie müssen mindestens eine Abwesenheit auswählen!</string> <string name="attendance_excuse_no_selection">Sie müssen mindestens eine Abwesenheit auswählen!</string>
<string name="attendance_excuse_title">Verzeihung</string> <string name="attendance_excuse_title">Verzeihung</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -466,6 +466,10 @@
<string name="channel_push">Push-Benachrichtigungen</string> <string name="channel_push">Push-Benachrichtigungen</string>
<string name="channel_upcoming_lessons">Bevorstehende Lektionen</string> <string name="channel_upcoming_lessons">Bevorstehende Lektionen</string>
<string name="channel_debug">Debuggen</string> <string name="channel_debug">Debuggen</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Ende der Unterstützung</string>
<string name="drop_kitkat_content">Wir beenden die Unterstützung für dein Gerät. Es werden keine neuen Funktionen mehr in Wulkanowy erscheinen. Allerdings werden wir bis Ende 2021 kritische Patches veröffentlichen, so dass du Zeit hast, zu einem neueren Modell zu wechseln</string>
<string name="drop_kitkat_again">Nicht mehr fragen</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Schwarz</string> <string name="all_black">Schwarz</string>
<string name="all_red">Rot</string> <string name="all_red">Rot</string>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!--Activity/Fragment title--> <!--Activity/Fragment title-->
<string name="login_title">Login</string> <string name="login_title">Prisijungti</string>
<string name="main_title">Wulkanowy</string> <string name="main_title">Wulkanowy</string>
<string name="grade_title">Laipsnis</string> <string name="grade_title">Laipsnis</string>
<string name="attendance_title">Attendance</string> <string name="attendance_title">Attendance</string>
@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Absence reason (optional)</string> <string name="attendance_excuse_dialog_reason">Absence reason (optional)</string>
<string name="attendance_excuse_dialog_submit">Send</string> <string name="attendance_excuse_dialog_submit">Send</string>
<string name="attendance_excuse_success">Absence excused successfully!</string> <string name="attendance_excuse_success">Absence excuse request sent successfully!</string>
<string name="attendance_excuse_no_selection">You must select at least one absence!</string> <string name="attendance_excuse_no_selection">You must select at least one absence!</string>
<string name="attendance_excuse_title">Excuse</string> <string name="attendance_excuse_title">Excuse</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -506,6 +506,10 @@
<string name="channel_push">Push notifications</string> <string name="channel_push">Push notifications</string>
<string name="channel_upcoming_lessons">Upcoming lessons</string> <string name="channel_upcoming_lessons">Upcoming lessons</string>
<string name="channel_debug">Debug</string> <string name="channel_debug">Debug</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">End of support</string>
<string name="drop_kitkat_content">We are ending support for your device. No more new features will appear for it in Wulkanowy. However, we will be releasing critical patches until the end of 2021 so you have time to switch to a newer model</string>
<string name="drop_kitkat_again">Don\'t show again</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Black</string> <string name="all_black">Black</string>
<string name="all_red">Red</string> <string name="all_red">Red</string>

View File

@ -49,4 +49,12 @@
<item name="android:navigationBarColor" tools:targetApi="lollipop">?colorSurface</item> <item name="android:navigationBarColor" tools:targetApi="lollipop">?colorSurface</item>
<item name="android:statusBarColor" tools:targetApi="lollipop">@android:color/black</item> <item name="android:statusBarColor" tools:targetApi="lollipop">@android:color/black</item>
</style> </style>
<style name="WulkanowyTheme.WidgetAccountSwitcher" parent="Theme.MaterialComponents.Dialog">
<item name="colorPrimary">@color/colorPrimaryLight</item>
<item name="colorSecondary">@color/colorPrimaryLight</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
</resources> </resources>

View File

@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Powód nieobecności (opcjonalny)</string> <string name="attendance_excuse_dialog_reason">Powód nieobecności (opcjonalny)</string>
<string name="attendance_excuse_dialog_submit">Wyślij</string> <string name="attendance_excuse_dialog_submit">Wyślij</string>
<string name="attendance_excuse_success">Usprawiedliwiono pomyślnie!</string> <string name="attendance_excuse_success">Prośba o usprawiedliwienie została pomyślnie wysłana!</string>
<string name="attendance_excuse_no_selection">Musisz wybrać co najmniej jedną nieobecność!</string> <string name="attendance_excuse_no_selection">Musisz wybrać co najmniej jedną nieobecność!</string>
<string name="attendance_excuse_title">Usprawiedliw</string> <string name="attendance_excuse_title">Usprawiedliw</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -317,7 +317,7 @@
<string name="lucky_number_history_button">Pokaż historię</string> <string name="lucky_number_history_button">Pokaż historię</string>
<!--Lucky number history--> <!--Lucky number history-->
<string name="lucky_number_history_title">Historia numerków</string> <string name="lucky_number_history_title">Historia numerków</string>
<string name="lucky_number_history_empty">Brak informacji o szczęśliwych numerach</string> <string name="lucky_number_history_empty">Brak informacji o szczęśliwych numerkach</string>
<!--Mobile devices--> <!--Mobile devices-->
<string name="mobile_devices_title">Dostęp mobilny</string> <string name="mobile_devices_title">Dostęp mobilny</string>
<string name="mobile_devices_no_items">Brak urządzeń</string> <string name="mobile_devices_no_items">Brak urządzeń</string>
@ -506,6 +506,10 @@
<string name="channel_push">Powiadomienia push</string> <string name="channel_push">Powiadomienia push</string>
<string name="channel_upcoming_lessons">Nadchodzące lekcje</string> <string name="channel_upcoming_lessons">Nadchodzące lekcje</string>
<string name="channel_debug">Debugowanie</string> <string name="channel_debug">Debugowanie</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Koniec wsparcia</string>
<string name="drop_kitkat_content">Kończymy wsparcie dla Twojego urządzenia. Nie pojawią się już dla niego żadne nowe funkcje w Wulkanowym. Będziemy jednak wypuszczać krytyczne poprawki do końca 2021 roku, abyś miał czas na przeniesienie się na nowszy model</string>
<string name="drop_kitkat_again">Nie pokazuj ponownie</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Czarny</string> <string name="all_black">Czarny</string>
<string name="all_red">Czerwony</string> <string name="all_red">Czerwony</string>

View File

@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Причина отсутствия (необязательно)</string> <string name="attendance_excuse_dialog_reason">Причина отсутствия (необязательно)</string>
<string name="attendance_excuse_dialog_submit">Послать</string> <string name="attendance_excuse_dialog_submit">Послать</string>
<string name="attendance_excuse_success">Статус отсутствия изменён</string> <string name="attendance_excuse_success">Запрос на освобождение оправдания успешно отправлен!</string>
<string name="attendance_excuse_no_selection">Выберите хотя-бы одно отсутствие</string> <string name="attendance_excuse_no_selection">Выберите хотя-бы одно отсутствие</string>
<string name="attendance_excuse_title">Изменить статус</string> <string name="attendance_excuse_title">Изменить статус</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -506,6 +506,10 @@
<string name="channel_push">Показывать push-уведомления</string> <string name="channel_push">Показывать push-уведомления</string>
<string name="channel_upcoming_lessons">Будущие уроки</string> <string name="channel_upcoming_lessons">Будущие уроки</string>
<string name="channel_debug">Дебаг</string> <string name="channel_debug">Дебаг</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Конец поддержки</string>
<string name="drop_kitkat_content">Поддержка заканчивается на вашем устройстве. В Wulkanowy больше не появятся новые возможности. Однако до конца 2021 года мы выпустим критические патчи, чтобы у вас было время перейти на новую модель</string>
<string name="drop_kitkat_again">Не показывать снова</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Чёрный</string> <string name="all_black">Чёрный</string>
<string name="all_red">Красный</string> <string name="all_red">Красный</string>

View File

@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Dôvod neprítomnosti (voliteľný)</string> <string name="attendance_excuse_dialog_reason">Dôvod neprítomnosti (voliteľný)</string>
<string name="attendance_excuse_dialog_submit">Poslať</string> <string name="attendance_excuse_dialog_submit">Poslať</string>
<string name="attendance_excuse_success">Neprítomnosť úspešne ospravedlnená!</string> <string name="attendance_excuse_success">Žiadosť o ospravedlnenie neprítomnosti bola úspešne odoslaná!</string>
<string name="attendance_excuse_no_selection">Musíte vybrať aspoň jednu neprítomnosť!</string> <string name="attendance_excuse_no_selection">Musíte vybrať aspoň jednu neprítomnosť!</string>
<string name="attendance_excuse_title">Ospravedlniť</string> <string name="attendance_excuse_title">Ospravedlniť</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -506,6 +506,10 @@
<string name="channel_push">Push upozornenia</string> <string name="channel_push">Push upozornenia</string>
<string name="channel_upcoming_lessons">Nadchádzajúce lekcie</string> <string name="channel_upcoming_lessons">Nadchádzajúce lekcie</string>
<string name="channel_debug">Ladenie</string> <string name="channel_debug">Ladenie</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Koniec podpory</string>
<string name="drop_kitkat_content">Ukončujeme podporu pre vaše zariadenie. V Wulkanovým už pre neho nebudú žiadne nové funkcie. Kritické opravy však budeme vydávať až do konca roka 2021, aby ste mali čas prejsť na novší model</string>
<string name="drop_kitkat_again">Nezobrazovať znovu</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Čierny</string> <string name="all_black">Čierny</string>
<string name="all_red">Červený</string> <string name="all_red">Červený</string>

View File

@ -46,7 +46,7 @@
<string name="login_expired_token">Минув термін дії токену</string> <string name="login_expired_token">Минув термін дії токену</string>
<string name="login_invalid_email">Недійсна адреса електронної пошти</string> <string name="login_invalid_email">Недійсна адреса електронної пошти</string>
<string name="login_invalid_login">Використовуйте призначений логін замість електронної пошти</string> <string name="login_invalid_login">Використовуйте призначений логін замість електронної пошти</string>
<string name="login_invalid_custom_email">Використовуйте призначений логін або електронну адресу в @% 1 $ s</string> <string name="login_invalid_custom_email">Використовуйте призначений логін або електронну адресу в @%1$s</string>
<string name="login_invalid_symbol">Неправильний симбвол</string> <string name="login_invalid_symbol">Неправильний симбвол</string>
<string name="login_incorrect_symbol">Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+</string> <string name="login_incorrect_symbol">Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+</string>
<string name="login_field_required">Обов\'язкове поле</string> <string name="login_field_required">Обов\'язкове поле</string>
@ -192,7 +192,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Причина відсутності (необов’язково)</string> <string name="attendance_excuse_dialog_reason">Причина відсутності (необов’язково)</string>
<string name="attendance_excuse_dialog_submit">Надіслати</string> <string name="attendance_excuse_dialog_submit">Надіслати</string>
<string name="attendance_excuse_success">Змінено статус відсутності</string> <string name="attendance_excuse_success">Запит на виправдання відсутності успішно надіслано!</string>
<string name="attendance_excuse_no_selection">Оберіть хоча б одну відсутність</string> <string name="attendance_excuse_no_selection">Оберіть хоча б одну відсутність</string>
<string name="attendance_excuse_title">Змінити статус</string> <string name="attendance_excuse_title">Змінити статус</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -506,6 +506,10 @@
<string name="channel_push">Показувати push-повідомлення</string> <string name="channel_push">Показувати push-повідомлення</string>
<string name="channel_upcoming_lessons">Наступні уроки</string> <string name="channel_upcoming_lessons">Наступні уроки</string>
<string name="channel_debug">Дебаг</string> <string name="channel_debug">Дебаг</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">Кінець підтримки</string>
<string name="drop_kitkat_content">Ми завершуємо підтримку вашого пристрою. Більше нових функцій не з\'явиться у Wulkanowy. Однак ми виробляємо критичні патчі до кінця 2021, тому у вас буде час перейти на новішу модель</string>
<string name="drop_kitkat_again">Не показувати знову</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Чорний</string> <string name="all_black">Чорний</string>
<string name="all_red">Червоний</string> <string name="all_red">Червоний</string>

View File

@ -194,7 +194,7 @@
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Absence reason (optional)</string> <string name="attendance_excuse_dialog_reason">Absence reason (optional)</string>
<string name="attendance_excuse_dialog_submit">Send</string> <string name="attendance_excuse_dialog_submit">Send</string>
<string name="attendance_excuse_success">Absence excused successfully!</string> <string name="attendance_excuse_success">Absence excuse request sent successfully!</string>
<string name="attendance_excuse_no_selection">You must select at least one absence!</string> <string name="attendance_excuse_no_selection">You must select at least one absence!</string>
<string name="attendance_excuse_title">Excuse</string> <string name="attendance_excuse_title">Excuse</string>
@ -541,6 +541,12 @@
<string name="channel_debug">Debug</string> <string name="channel_debug">Debug</string>
<!--Drop kitkat alert dialog strings-->
<string name="drop_kitkat_title">End of support</string>
<string name="drop_kitkat_content">We are ending support for your device. No more new features will appear for it in Wulkanowy. However, we will be releasing critical patches until the end of 2021 so you have time to switch to a newer model</string>
<string name="drop_kitkat_again">Don\'t show again</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Black</string> <string name="all_black">Black</string>
<string name="all_red">Red</string> <string name="all_red">Red</string>
@ -554,6 +560,7 @@
<string name="all_copied">Copied</string> <string name="all_copied">Copied</string>
<string name="all_undo">Undo</string> <string name="all_undo">Undo</string>
<!--Update helper--> <!--Update helper-->
<string name="update_download_started">Download of updates has started…</string> <string name="update_download_started">Download of updates has started…</string>
<string name="update_download_success">An update has just been downloaded.</string> <string name="update_download_success">An update has just been downloaded.</string>

View File

@ -26,7 +26,7 @@
<item name="android:windowBackground">@drawable/layer_splash_background</item> <item name="android:windowBackground">@drawable/layer_splash_background</item>
</style> </style>
<style name="WulkanowyTheme.WidgetAccountSwitcher" parent="Theme.MaterialComponents.DayNight.Dialog"> <style name="WulkanowyTheme.WidgetAccountSwitcher" parent="Theme.MaterialComponents.Light.Dialog">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorSecondary">@color/colorPrimary</item> <item name="colorSecondary">@color/colorPrimary</item>
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>

View File

@ -54,7 +54,7 @@ class RecipientLocalTest {
coEvery { recipientDb.deleteAll(any()) } just Runs coEvery { recipientDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(1, 123, "", 4, "", listOf()), 7) } val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(4, 123, "", 4, "", listOf()), 7) }
// verify // verify
assertEquals(3, res.size) assertEquals(3, res.size)
@ -73,7 +73,7 @@ class RecipientLocalTest {
coEvery { recipientDb.deleteAll(any()) } just Runs coEvery { recipientDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(1, 123, "", 4, "", listOf()), 7) } val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(4, 123, "", 4, "", listOf()), 7) }
// verify // verify
assertEquals(3, res.size) assertEquals(3, res.size)

View File

@ -440,15 +440,71 @@ class GradeAverageProviderTest {
) )
} }
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
student,
semesters[2].semesterId,
true
).getResult()
}
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 assertEquals(
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 3.0,
items.single { it.subject == "Matematyka" }.average,
.0
) // (from details): 3,5 + 2,5 → 3,0
assertEquals(
3.25,
items.single { it.subject == "Fizyka" }.average,
.0
) // (from details): 3,5 + 3,0 → 3,25
} }
@Test @Test
fun `calc full year average when current is second with load from cache sequence`() { fun `calc all year average`() {
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
every { preferencesRepository.gradeAverageForceCalc } returns false
coEvery {
gradeRepository.getGrades(
student,
semesters[1],
true
)
} returns flowWithResource {
firstGrades to listOf(
getSummary(22, "Matematyka", .0),
getSummary(22, "Fizyka", .0)
)
}
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
} returns flowWithResource {
secondGrades to listOf(
getSummary(22, "Matematyka", .0),
getSummary(22, "Fizyka", .0)
)
}
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
student,
semesters[2].semesterId,
true
).getResult()
}
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
}
@Test
fun `force calc full year average when current is second with load from cache sequence`() {
every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageForceCalc } returns true
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { semesterRepository.getSemesters(student) } returns semesters
@ -490,17 +546,39 @@ class GradeAverageProviderTest {
} }
@Test @Test
fun `calc both semesters average when no summaries`() { fun `force calc both semesters average when no summaries`() {
every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageForceCalc } returns true
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() } coEvery {
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() } gradeRepository.getGrades(
student,
semesters[1],
true
)
} returns flowWithResource { firstGrades to emptyList() }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
} returns flowWithResource { secondGrades to emptyList() }
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
student,
semesters[2].semesterId,
true
).getResult()
}
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 assertEquals(
3.0,
items.single { it.subject == "Matematyka" }.average,
.0
) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
} }

View File

@ -1,10 +1,13 @@
package io.github.wulkanowy.ui.modules.splash package io.github.wulkanowy.ui.modules.splash
import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.MainCoroutineRule
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.verify import io.mockk.verify
import org.junit.Before import org.junit.Before
@ -22,6 +25,12 @@ class SplashPresenterTest {
@MockK @MockK
lateinit var studentRepository: StudentRepository lateinit var studentRepository: StudentRepository
@MockK
lateinit var preferencesRepository: PreferencesRepository
@MockK
lateinit var appInfo: AppInfo
@MockK(relaxed = true) @MockK(relaxed = true)
lateinit var errorHandler: ErrorHandler lateinit var errorHandler: ErrorHandler
@ -30,19 +39,25 @@ class SplashPresenterTest {
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
presenter = SplashPresenter(errorHandler, studentRepository) presenter = SplashPresenter(errorHandler, studentRepository, preferencesRepository, appInfo)
} }
@Test @Test
fun testOpenLoginView() { fun testOpenLoginView() {
every { appInfo.systemVersion } returns 30
every { preferencesRepository.isKitkatDialogDisabled } returns true
coEvery { studentRepository.isCurrentStudentSet() } returns false coEvery { studentRepository.isCurrentStudentSet() } returns false
presenter.onAttachView(splashView, null) presenter.onAttachView(splashView, null)
verify { splashView.openLoginView() } verify { splashView.openLoginView() }
} }
@Test @Test
fun testMainMainView() { fun testMainMainView() {
every { appInfo.systemVersion } returns 30
every { preferencesRepository.isKitkatDialogDisabled } returns true
coEvery { studentRepository.isCurrentStudentSet() } returns true coEvery { studentRepository.isCurrentStudentSet() } returns true
presenter.onAttachView(splashView, null) presenter.onAttachView(splashView, null)
verify { splashView.openMainView() } verify { splashView.openMainView() }
} }

View File

@ -1,6 +1,6 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.4.31' kotlin_version = '1.4.32'
about_libraries = '8.8.4' about_libraries = '8.8.4'
hilt_version = "2.33-beta" hilt_version = "2.33-beta"
} }
@ -13,12 +13,13 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.android.tools.build:gradle:4.1.3'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.huawei.agconnect:agcp:1.5.0.300' classpath 'com.huawei.agconnect:agcp:1.5.1.200'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
classpath "com.github.triplet.gradle:play-publisher:2.8.0" classpath "com.github.triplet.gradle:play-publisher:2.8.0"
classpath "ru.cian:huawei-publish-gradle-plugin:1.2.2"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"