Compare commits
161 Commits
Author | SHA1 | Date | |
---|---|---|---|
b60c59216d | |||
d20f3180cf | |||
17761af9d6 | |||
067817bace | |||
800a31f160 | |||
8b83b37b09 | |||
43e95cfdc6 | |||
ae2a697e01 | |||
3f5fbbc71b | |||
206b40ce1b | |||
43c56b5534 | |||
a1076539dc | |||
3c48264539 | |||
539cf2207b | |||
f0e897713c | |||
a448092008 | |||
f5b46707ff | |||
e6247d4428 | |||
d0869b235a | |||
464900d95b | |||
368274239e | |||
ee33197494 | |||
d3ea743707 | |||
976d4b8ce2 | |||
b77fc0d32a | |||
bd3716609e | |||
5b87cc9009 | |||
4f7be8d2cb | |||
8733e7782f | |||
e03b0dfa01 | |||
efafd2094a | |||
1560335749 | |||
5bee155f1e | |||
c9dc9a323f | |||
87e7e00705 | |||
57681b35ea | |||
8fb09d7b7d | |||
168f750863 | |||
3e1acbd3bf | |||
21ef2adcf6 | |||
3f6159e976 | |||
555b5ec112 | |||
60a9bcae46 | |||
eeb1341c1f | |||
c77b50d51b | |||
8644ce32d5 | |||
94506aca52 | |||
eee4e1f4b5 | |||
c1942d012f | |||
fe846b463a | |||
0ea2e68249 | |||
be0445b227 | |||
48249f3093 | |||
8d7110735d | |||
94957850c3 | |||
fa2cfc8427 | |||
3d467c43ba | |||
b76032044d | |||
495b84204c | |||
ea4b299de6 | |||
acb5e2afd4 | |||
50863d6ac2 | |||
e15eb03299 | |||
be48791d51 | |||
c0e1a5b401 | |||
cb1b467a21 | |||
f14346ff32 | |||
af8108a649 | |||
5743928126 | |||
388d37bf9c | |||
d572fc737f | |||
47b0f1b527 | |||
1afa7ecf3c | |||
9139febbdf | |||
2dd0b56333 | |||
3b970209a5 | |||
6f590eb194 | |||
b20b8fb243 | |||
7e4a212951 | |||
6dfeed3a26 | |||
06a27199ee | |||
76039e5eb9 | |||
6a4aaff8d6 | |||
9e2985864a | |||
412057b512 | |||
963caadced | |||
8388a8a5fc | |||
a801c8f8be | |||
9d8ad73e63 | |||
182f6c8a81 | |||
1e4a3536cf | |||
169a314664 | |||
904eed648b | |||
3bb94adece | |||
105b70fcad | |||
b4bf7c7589 | |||
ff425d6d2b | |||
4fceb854b3 | |||
3adac154b4 | |||
a320cf8f7c | |||
1ed0884dfd | |||
021e9726c6 | |||
3c438757e3 | |||
99c4a65df5 | |||
2c442fc87b | |||
44ba0d76de | |||
17aa77ad41 | |||
c18302b812 | |||
de8e9bde49 | |||
000cbd11a2 | |||
5e3b89636f | |||
dd085a14fa | |||
bfe558d887 | |||
4841a0439d | |||
cac76857c4 | |||
6b8b7c9143 | |||
53584026dc | |||
7900ad913f | |||
8ec844a8fe | |||
19ff953ab2 | |||
e29c211cf2 | |||
2492a9c204 | |||
61de1de532 | |||
b5862da776 | |||
e0c802bf67 | |||
dcac138ff8 | |||
23411a608f | |||
87facd2663 | |||
bad0776cab | |||
a063aabc7c | |||
1fdbdf34b9 | |||
624fd71dbb | |||
dfa10883d3 | |||
aff40df707 | |||
42f9594210 | |||
3e3a080b70 | |||
39534aeda4 | |||
e39b053d2d | |||
28fd7460cb | |||
82b207b03a | |||
4984cb9b26 | |||
e2ba265048 | |||
d1cd497a23 | |||
dd5ce752da | |||
e5e95e7dec | |||
f5e4c63fed | |||
70d42bb864 | |||
82df5b9515 | |||
6568c4abf8 | |||
d79b1c9a58 | |||
26565b627a | |||
c0a53cb90c | |||
2bcbac5ab3 | |||
5581fdcab8 | |||
6a8161cd98 | |||
e0b067fadd | |||
f37ddfe00f | |||
3a887f597b | |||
52d359827e | |||
a70ccbb0d0 | |||
2b6386c522 |
74
.github/workflows/deploy-store.yml
vendored
Normal file
74
.github/workflows/deploy-store.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
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: Prepare credentials
|
||||
env:
|
||||
AGC_CREDENTIALS: ${{ secrets.AGC_CREDENTIALS }}
|
||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||
- name: Build and publish HMS version
|
||||
env:
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew assembleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
144
.github/workflows/deploy-test.yml
vendored
Normal file
144
.github/workflows/deploy-test.yml
vendored
Normal 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
|
118
.github/workflows/test.yml
vendored
118
.github/workflows/test.yml
vendored
@ -1,48 +1,21 @@
|
||||
name: Test and deploy
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
branches: [ master, develop ]
|
||||
tags: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
|
||||
workflow_dispatch:
|
||||
branches: [ master, develop ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Pre-build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- name: Build
|
||||
run: ./gradlew --build-cache compileFdroidDebugUnitTestKotlin preFdroidDebugAndroidTestBuild dexBuilderFdroidDebugAndroidTest packageFdroidDebug packageFdroidDebugAndroidTest
|
||||
- name: Prepare build cache
|
||||
run: tar -cf prebuild.tar .build-cache .gradle app/build
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
path: prebuild.tar
|
||||
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: [ build ]
|
||||
steps:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
@ -52,11 +25,6 @@ jobs:
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- name: Unit tests
|
||||
run: |
|
||||
./gradlew --build-cache -Pcoverage testFdroidDebugUnitTest --stacktrace
|
||||
@ -64,79 +32,3 @@ jobs:
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
flags: unit
|
||||
|
||||
instrumentation-tests:
|
||||
name: Instrumentation tests
|
||||
runs-on: macOS-latest
|
||||
timeout-minutes: 15
|
||||
needs: [ build ]
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
api-level: [21, 29]
|
||||
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*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- name: Instrumentation tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
arch: x86
|
||||
script: |
|
||||
./gradlew --build-cache -Pcoverage connectedFdroidDebugAndroidTest --stacktrace
|
||||
./gradlew --build-cache -Pcoverage jacocoTestReport --stacktrace
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
flags: instrumented,api-${{ matrix.api-level }}
|
||||
|
||||
deploy-google-play:
|
||||
name: Deploy to google play
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
environment: google-play
|
||||
needs: [ build, unit-tests, instrumentation-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*') }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: prebuild.tar
|
||||
- name: Extract build cache
|
||||
run: tar -xf prebuild.tar
|
||||
- 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;
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -117,3 +117,4 @@ Thumbs.db
|
||||
|
||||
|
||||
app/src/release/agconnect-services.json
|
||||
app/src/release/agconnect-credentials.json
|
||||
|
15
.idea/codeStyles/Project.xml
generated
15
.idea/codeStyles/Project.xml
generated
@ -18,18 +18,9 @@
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="false" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
|
||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<MarkdownNavigatorCodeStyleSettings>
|
||||
<option name="RIGHT_MARGIN" value="72" />
|
||||
</MarkdownNavigatorCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
@ -143,13 +134,11 @@
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
|
11
README.en.md
11
README.en.md
@ -12,7 +12,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
|
||||
|
||||
## Features
|
||||
|
||||
* logging in using the email and password OR using token and pin
|
||||
* logging in using the email and password
|
||||
* functions from the register website:
|
||||
* grades
|
||||
* grade statistics
|
||||
@ -25,15 +25,19 @@ Unofficial android VULCAN UONET+ register client for both students and their par
|
||||
* homework
|
||||
* notes
|
||||
* lucky number
|
||||
* additional lessons
|
||||
* school conferences
|
||||
* student and school information
|
||||
* calculation of the average independently of school's preferences
|
||||
* notifications, e.g. about a new grade
|
||||
* support for multiple accounts with the ability to rename students
|
||||
* dark and black (AMOLED) theme
|
||||
* offline mode
|
||||
* no ads
|
||||
|
||||
## Download
|
||||
|
||||
You can download the current beta version from the Google Play, F-Droid or Huawei AppGallery store
|
||||
You can download the current version from the Google Play, F-Droid or Huawei AppGallery store
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play"
|
||||
@ -60,6 +64,9 @@ You can also download a [development version](https://wulkanowy.github.io/#downl
|
||||
|
||||
Please contribute to the project either by creating a PR or submitting an issue on GitHub.
|
||||
|
||||
For people interested in translating the application into different languages, we provide Crowdin
|
||||
https://crowdin.com/project/wulkanowy2
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details
|
||||
|
13
README.md
13
README.md
@ -12,7 +12,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
|
||||
## Funkcje
|
||||
|
||||
* logowanie za pomocą e-maila i hasła LUB tokena i pinu
|
||||
* logowanie za pomocą e-maila i hasła
|
||||
* funkcje ze strony internetowej dziennika:
|
||||
* oceny
|
||||
* statystyki ocen
|
||||
@ -25,15 +25,19 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
* zadania domowe
|
||||
* uwagi
|
||||
* szczęśliwy numerek
|
||||
* dodatkowe lekcje
|
||||
* zebrania w szkole
|
||||
* informacje o uczniu i szkole
|
||||
* obliczanie średniej niezależnie od preferencji szkoły
|
||||
* powiadomienia np. o nowej ocenie
|
||||
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
|
||||
* ciemny i czarny (AMOLED) motyw
|
||||
* tryb offilne
|
||||
* tryb offline
|
||||
* brak reklam
|
||||
|
||||
## Pobierz
|
||||
|
||||
Aktualną wersję beta możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
|
||||
Aktualną wersję możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Pobierz z Google Play"
|
||||
@ -61,6 +65,9 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa
|
||||
|
||||
Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub.
|
||||
|
||||
Dla osób zainteresowanych tłumaczeniem aplikacji na różne języki udostępniamy Crowdina
|
||||
https://crowdin.com/project/wulkanowy2
|
||||
|
||||
## Licencja
|
||||
|
||||
Ten projekt udostępniany jest na licencji Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE)
|
||||
|
BIN
app/bitrise.jks.gpg
Normal file
BIN
app/bitrise.jks.gpg
Normal file
Binary file not shown.
@ -4,26 +4,32 @@ apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
apply plugin: 'ru.cian.huawei-publish'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.huawei.agconnect'
|
||||
apply from: 'jacoco.gradle'
|
||||
apply from: 'sonarqube.gradle'
|
||||
apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
buildToolsVersion '30.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 30
|
||||
versionCode 81
|
||||
versionName "0.24.3"
|
||||
versionCode 92
|
||||
versionName "1.1.6"
|
||||
multiDexEnabled true
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase")
|
||||
]
|
||||
@ -38,7 +44,8 @@ android {
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
// https://github.com/robolectric/robolectric/issues/3928#issuecomment-395309991
|
||||
debug.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@ -100,6 +107,10 @@ android {
|
||||
disable 'HardwareIds'
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -107,6 +118,7 @@ android {
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
useIR = true
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
|
||||
}
|
||||
@ -125,32 +137,43 @@ play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'alpha'
|
||||
track = 'production'
|
||||
updatePriority = 5
|
||||
}
|
||||
|
||||
huaweiPublish {
|
||||
instances {
|
||||
hmsRelease {
|
||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||
buildFormat = "apk"
|
||||
deployType = "draft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.4.0"
|
||||
room = "2.2.6"
|
||||
work_manager = "2.5.0"
|
||||
work_hilt = "1.0.0-beta01"
|
||||
room = "2.3.0-rc01"
|
||||
chucker = "3.4.0"
|
||||
mockk = "1.10.5"
|
||||
mockk = "1.11.0"
|
||||
moshi = "1.11.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:0.24.1"
|
||||
implementation "io.github.wulkanowy:sdk:1.1.6"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
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.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.activity:activity-ktx:1.2.2"
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat-resources:1.2.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.2"
|
||||
implementation "androidx.annotation:annotation:1.2.0"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
@ -159,15 +182,15 @@ dependencies {
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||
implementation "com.google.android.material:material:1.2.1"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
||||
implementation "com.google.android.material:material:1.3.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.2.0"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
|
||||
implementation 'com.mikhaellopez:circularimageview:4.2.0'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
||||
|
||||
implementation "androidx.room:room-runtime:$room"
|
||||
implementation "androidx.room:room-ktx:$room"
|
||||
@ -175,12 +198,12 @@ dependencies {
|
||||
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0-alpha02'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
|
||||
implementation "androidx.hilt:hilt-work:$work_hilt"
|
||||
kapt "androidx.hilt:hilt-compiler:$work_hilt"
|
||||
|
||||
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
||||
implementation "com.ncapdevi:frag-nav:3.3.0"
|
||||
implementation "com.github.YarikSOffice:lingver:1.2.2"
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation "com.squareup.moshi:moshi:$moshi"
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
||||
@ -194,34 +217,36 @@ dependencies {
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:26.3.0')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:26.7.0')
|
||||
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-crashlytics:'
|
||||
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.1.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.2.301'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.1.200'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
||||
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
|
||||
|
||||
testImplementation "junit:junit:4.13.1"
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
testImplementation "androidx.test:runner:1.3.0"
|
||||
testImplementation "androidx.test.ext:junit:1.1.2"
|
||||
testImplementation "androidx.test:core:1.3.0"
|
||||
testImplementation "androidx.room:room-testing:$room"
|
||||
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
|
||||
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.3.0"
|
||||
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||
androidTestImplementation "io.mockk:mockk-android:$mockk"
|
||||
androidTestImplementation "androidx.room:room-testing:$room"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.huawei.agconnect'
|
||||
|
29
app/proguard-rules.pro
vendored
29
app/proguard-rules.pro
vendored
@ -1,33 +1,21 @@
|
||||
# Optimizations
|
||||
-optimizationpasses 5
|
||||
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontskipnonpubliclibraryclassmembers
|
||||
-dontpreverify
|
||||
# General
|
||||
-dontobfuscate
|
||||
-allowaccessmodification
|
||||
-repackageclasses ''
|
||||
-verbose
|
||||
|
||||
|
||||
#Keep all wulkanowy files
|
||||
#Config for wulkanowy
|
||||
-keep class io.github.wulkanowy.** {*;}
|
||||
|
||||
|
||||
#Config for anallitycs
|
||||
-keepattributes *Annotation*
|
||||
#Config for firebase crashlitycs
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.crashlytics.** {*;}
|
||||
-keep public class * extends java.lang.Exception
|
||||
-dontwarn com.crashlytics.**
|
||||
|
||||
|
||||
#Config for OkHttp
|
||||
#Config for Okio and OkHttp
|
||||
-dontwarn javax.annotation.**
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
|
||||
#Config for MPAndroidChart
|
||||
@ -36,10 +24,3 @@
|
||||
|
||||
#Config for Material Components
|
||||
-keep class com.google.android.material.tabs.** { *; }
|
||||
|
||||
|
||||
#Config for About Libraries
|
||||
-keep class .R
|
||||
-keep class **.R$* {
|
||||
<fields>;
|
||||
}
|
||||
|
2136
app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json
Normal file
2136
app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json
Normal file
File diff suppressed because it is too large
Load Diff
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
Normal file
File diff suppressed because it is too large
Load Diff
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/33.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/33.json
Normal file
File diff suppressed because it is too large
Load Diff
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/34.json
Normal file
File diff suppressed because it is too large
Load Diff
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
package io.github.wulkanowy.data
|
||||
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
class TestDispatchersProvider : DispatchersProvider() {
|
||||
|
||||
override val backgroundThread: CoroutineDispatcher
|
||||
get() = Dispatchers.Unconfined
|
||||
}
|
19
app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml
Normal file
19
app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="0.96"
|
||||
android:translateY="0.96">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M3.9512,2A2,2 0,0 0,2 4L2,18A2,2 0,0 0,4 20L10.0996,20C11.3596,21.24 13.09,22 15,22A7,7 0,0 0,15.7988 21.9551L15.7988,19.7832A4.85,4.85 0,0 1,15 19.8496C12.32,19.8496 10.1504,17.68 10.1504,15A4.85,4.85 0,0 1,15 10.1504C17.4677,10.1504 19.4978,11.9912 19.8047,14.375C20.566,14.3758 21.3108,14.5325 21.9922,14.834C21.9491,12.9905 21.2036,11.3226 20,10.0996L20,4A2,2 0,0 0,18 2L4,2A2,2 0,0 0,3.9512 2zM4,5L10,5L10,8L4,8L4,5zM12,5L18,5L18,8L12,8L12,5zM4,10L10.0996,10C9.2596,10.82 8.6291,11.85 8.2891,13L4,13L4,10zM14,12L14,15.6895L15.7988,16.7266L15.7988,14.9922L15.5,14.8203L15.5,12L14,12zM4,15L8,15C8,16.07 8.2399,17.09 8.6699,18L4,18L4,15z" />
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m17.298,24v-8.1249h2.5c0.7143,0 1.3523,0.1618 1.9141,0.4855 0.5655,0.3199 1.0063,0.7775 1.3225,1.3728 0.3162,0.5915 0.4743,1.2649 0.4743,2.0201v0.3739c0,0.7552 -0.1562,1.4267 -0.4687,2.0145 -0.3088,0.5878 -0.7459,1.0435 -1.3114,1.3672C21.1633,23.8326 20.5253,23.9963 19.8148,24ZM18.9721,17.2311v5.4241h0.8091c0.6548,0 1.1551,-0.2139 1.5011,-0.6417 0.346,-0.4278 0.5227,-1.0398 0.5301,-1.8359v-0.4297c0,-0.8259 -0.1711,-1.4509 -0.5134,-1.875 -0.3423,-0.4278 -0.8426,-0.6417 -1.5011,-0.6417z" />
|
||||
</group>
|
||||
</vector>
|
BIN
app/src/debug/res/drawable-hdpi/ic_stat_timetable.png
Normal file
BIN
app/src/debug/res/drawable-hdpi/ic_stat_timetable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 426 B |
BIN
app/src/debug/res/drawable-mdpi/ic_stat_timetable.png
Normal file
BIN
app/src/debug/res/drawable-mdpi/ic_stat_timetable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 335 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png
Normal file
BIN
app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 519 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png
Normal file
BIN
app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 700 B |
@ -18,6 +18,18 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="mailto" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="tel" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
@ -44,7 +56,7 @@
|
||||
android:name=".ui.modules.login.LoginActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/login_title"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||
android:theme="@style/WulkanowyTheme.Login"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.modules.main.MainActivity"
|
||||
@ -56,7 +68,7 @@
|
||||
android:name=".ui.modules.message.send.SendMessageActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/send_message_title"
|
||||
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||
android:theme="@style/WulkanowyTheme.MessageSend"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
|
@ -34,5 +34,17 @@
|
||||
{
|
||||
"displayName": "Mateusz Idziejczak",
|
||||
"githubUsername": "Luncenok"
|
||||
},
|
||||
{
|
||||
"displayName": "MRmlik12",
|
||||
"githubUsername": "MRmlik12"
|
||||
},
|
||||
{
|
||||
"displayName": "Damian Czupryn",
|
||||
"githubUsername": "Daxxxis"
|
||||
},
|
||||
{
|
||||
"displayName": "Kamil Studziński",
|
||||
"githubUsername": "studzinskik"
|
||||
}
|
||||
]
|
||||
|
@ -1,10 +1,13 @@
|
||||
package io.github.wulkanowy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log.DEBUG
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import android.webkit.WebView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
@ -45,24 +48,26 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageWarning")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Lingver.init(this)
|
||||
FragmentManager.enableNewStateManager(false)
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
|
||||
initLogging()
|
||||
logCurrentLanguage()
|
||||
fixWebViewLocale()
|
||||
}
|
||||
|
||||
private fun initLogging() {
|
||||
if (appInfo.isDebug) {
|
||||
Timber.plant(DebugLogTree())
|
||||
Timber.plant(FileLoggerTree.Builder()
|
||||
.withFileName("wulkanowy.%g.log")
|
||||
.withDirName(applicationContext.filesDir.absolutePath)
|
||||
.withFileLimit(10)
|
||||
.withMinPriority(DEBUG)
|
||||
.build()
|
||||
Timber.plant(
|
||||
FileLoggerTree.Builder()
|
||||
.withFileName("wulkanowy.%g.log")
|
||||
.withDirName(applicationContext.filesDir.absolutePath)
|
||||
.withFileLimit(10)
|
||||
.withMinPriority(DEBUG)
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
Timber.plant(CrashLogExceptionTree())
|
||||
@ -71,14 +76,24 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
|
||||
}
|
||||
|
||||
private fun logCurrentLanguage() {
|
||||
val newLang = if (preferencesRepository.appLanguage == "system") {
|
||||
appInfo.systemLanguage
|
||||
} else {
|
||||
preferencesRepository.appLanguage
|
||||
}
|
||||
private fun initializeAppLanguage() {
|
||||
Lingver.init(this)
|
||||
|
||||
analyticsHelper.logEvent("language", "startup" to newLang)
|
||||
if (preferencesRepository.appLanguage == "system") {
|
||||
Lingver.getInstance().setFollowSystemLocale(this)
|
||||
analyticsHelper.logEvent("language", "startup" to appInfo.systemLanguage)
|
||||
} else {
|
||||
analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixWebViewLocale() {
|
||||
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
|
||||
try {
|
||||
WebView(this).destroy()
|
||||
} catch (e: Exception) {
|
||||
//Ignore exceptions
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration() = Configuration.Builder()
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -33,17 +34,21 @@ internal class RepositoryModule {
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
|
||||
// for debug only
|
||||
addInterceptor(ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChuckerCollector(@ApplicationContext context: Context, prefRepository: PreferencesRepository): ChuckerCollector {
|
||||
fun provideChuckerCollector(
|
||||
@ApplicationContext context: Context,
|
||||
prefRepository: PreferencesRepository
|
||||
): ChuckerCollector {
|
||||
return ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
@ -53,7 +58,11 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideDatabase(@ApplicationContext context: Context, sharedPrefProvider: SharedPrefProvider) = AppDatabase.newInstance(context, sharedPrefProvider)
|
||||
fun provideDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
appInfo: AppInfo
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -65,7 +74,8 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -89,7 +99,8 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGradeSemesterStatisticsDao(database: AppDatabase) = database.gradeSemesterStatisticsDao
|
||||
fun provideGradeSemesterStatisticsDao(database: AppDatabase) =
|
||||
database.gradeSemesterStatisticsDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -166,4 +177,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
@ -28,6 +27,7 @@ import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
@ -53,6 +53,7 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.db.entities.Teacher
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
@ -80,12 +81,18 @@ import io.github.wulkanowy.data.db.migrations.Migration28
|
||||
import io.github.wulkanowy.data.db.migrations.Migration29
|
||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration30
|
||||
import io.github.wulkanowy.data.db.migrations.Migration31
|
||||
import io.github.wulkanowy.data.db.migrations.Migration32
|
||||
import io.github.wulkanowy.data.db.migrations.Migration33
|
||||
import io.github.wulkanowy.data.db.migrations.Migration34
|
||||
import io.github.wulkanowy.data.db.migrations.Migration35
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -116,6 +123,7 @@ import javax.inject.Singleton
|
||||
School::class,
|
||||
Conference::class,
|
||||
TimetableAdditional::class,
|
||||
StudentInfo::class,
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -124,50 +132,55 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 30
|
||||
const val VERSION_SCHEMA = 35
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6(),
|
||||
Migration7(),
|
||||
Migration8(),
|
||||
Migration9(),
|
||||
Migration10(),
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15(),
|
||||
Migration16(),
|
||||
Migration17(),
|
||||
Migration18(),
|
||||
Migration19(sharedPrefProvider),
|
||||
Migration20(),
|
||||
Migration21(),
|
||||
Migration22(),
|
||||
Migration23(),
|
||||
Migration24(),
|
||||
Migration25(),
|
||||
Migration26(),
|
||||
Migration27(),
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
)
|
||||
}
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6(),
|
||||
Migration7(),
|
||||
Migration8(),
|
||||
Migration9(),
|
||||
Migration10(),
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15(),
|
||||
Migration16(),
|
||||
Migration17(),
|
||||
Migration18(),
|
||||
Migration19(sharedPrefProvider),
|
||||
Migration20(),
|
||||
Migration21(),
|
||||
Migration22(),
|
||||
Migration23(),
|
||||
Migration24(),
|
||||
Migration25(),
|
||||
Migration26(),
|
||||
Migration27(),
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
Migration31(),
|
||||
Migration32(),
|
||||
Migration33(),
|
||||
Migration34(),
|
||||
Migration35(appInfo)
|
||||
)
|
||||
|
||||
fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
.setJournalMode(TRUNCATE)
|
||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.addMigrations(*getMigrations(sharedPrefProvider))
|
||||
.build()
|
||||
}
|
||||
fun newInstance(
|
||||
context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
appInfo: AppInfo
|
||||
) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
.setJournalMode(TRUNCATE)
|
||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.addMigrations(*getMigrations(sharedPrefProvider, appInfo))
|
||||
.build()
|
||||
}
|
||||
|
||||
abstract val studentDao: StudentDao
|
||||
@ -219,4 +232,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val conferenceDao: ConferenceDao
|
||||
|
||||
abstract val timetableAdditionalDao: TimetableAdditionalDao
|
||||
|
||||
abstract val studentInfoDao: StudentInfoDao
|
||||
}
|
||||
|
@ -13,4 +13,7 @@ interface LuckyNumberDao : BaseDao<LuckyNumber> {
|
||||
|
||||
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date")
|
||||
fun load(studentId: Int, date: LocalDate): Flow<LuckyNumber?>
|
||||
|
||||
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date >= :start AND date <= :end")
|
||||
fun getAll(studentId: Int, start: LocalDate, end: LocalDate): Flow<List<LuckyNumber>>
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ import javax.inject.Singleton
|
||||
@Dao
|
||||
interface RecipientDao : BaseDao<Recipient> {
|
||||
|
||||
@Query("SELECT * FROM Recipients WHERE student_id = :userLoginId AND unit_id = :unitId AND role = :role")
|
||||
suspend fun loadAll(userLoginId: Int, unitId: Int, role: Int): List<Recipient>
|
||||
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role")
|
||||
suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient>
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.ABORT
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,11 +22,14 @@ interface StudentDao {
|
||||
@Delete
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
suspend fun loadById(id: Int): Student?
|
||||
suspend fun loadById(id: Long): Student?
|
||||
|
||||
@Query("SELECT * FROM Students")
|
||||
suspend fun loadAll(): List<Student>
|
||||
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface StudentInfoDao : BaseDao<StudentInfo> {
|
||||
|
||||
@Query("SELECT * FROM StudentInfo WHERE student_id = :studentId")
|
||||
fun loadStudentInfo(studentId: Int): Flow<StudentInfo?>
|
||||
}
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime
|
||||
data class Message(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
val studentId: Long,
|
||||
|
||||
@ColumnInfo(name = "real_id")
|
||||
val realId: Int,
|
||||
|
@ -9,7 +9,7 @@ import java.io.Serializable
|
||||
data class Recipient(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val userLoginId: Int,
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "real_id")
|
||||
val realId: String,
|
||||
|
@ -7,7 +7,13 @@ import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id", "class_id"], unique = true)])
|
||||
@Entity(
|
||||
tableName = "Students",
|
||||
indices = [Index(
|
||||
value = ["email", "symbol", "student_id", "school_id", "class_id"],
|
||||
unique = true
|
||||
)]
|
||||
)
|
||||
data class Student(
|
||||
|
||||
@ColumnInfo(name = "scrapper_base_url")
|
||||
@ -52,7 +58,7 @@ data class Student(
|
||||
@ColumnInfo(name = "school_id")
|
||||
val schoolSymbol: String,
|
||||
|
||||
@ColumnInfo(name ="school_short")
|
||||
@ColumnInfo(name = "school_short")
|
||||
val schoolShortName: String,
|
||||
|
||||
@ColumnInfo(name = "school_name")
|
||||
@ -73,4 +79,9 @@ data class Student(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
var nick = ""
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor = 0L
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.data.enums.Gender
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDate
|
||||
|
||||
@Entity(tableName = "StudentInfo")
|
||||
data class StudentInfo(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "full_name")
|
||||
val fullName: String,
|
||||
|
||||
@ColumnInfo(name = "first_name")
|
||||
val firstName: String,
|
||||
|
||||
@ColumnInfo(name = "second_name")
|
||||
val secondName: String,
|
||||
|
||||
val surname: String,
|
||||
|
||||
@ColumnInfo(name = "birth_date")
|
||||
val birthDate: LocalDate,
|
||||
|
||||
@ColumnInfo(name = "birth_place")
|
||||
val birthPlace: String,
|
||||
|
||||
val gender: Gender,
|
||||
|
||||
@ColumnInfo(name = "has_polish_citizenship")
|
||||
val hasPolishCitizenship: Boolean,
|
||||
|
||||
@ColumnInfo(name = "family_name")
|
||||
val familyName: String,
|
||||
|
||||
@ColumnInfo(name = "parents_names")
|
||||
val parentsNames: String,
|
||||
|
||||
val address: String,
|
||||
|
||||
@ColumnInfo(name = "registered_address")
|
||||
val registeredAddress: String,
|
||||
|
||||
@ColumnInfo(name = "correspondence_address")
|
||||
val correspondenceAddress: String,
|
||||
|
||||
@ColumnInfo(name = "phone_number")
|
||||
val phoneNumber: String,
|
||||
|
||||
@ColumnInfo(name = "cell_phone_number")
|
||||
val cellPhoneNumber: String,
|
||||
|
||||
val email: String,
|
||||
|
||||
@Embedded(prefix = "first_guardian_")
|
||||
val firstGuardian: StudentGuardian?,
|
||||
|
||||
@Embedded(prefix = "second_guardian_")
|
||||
val secondGuardian: StudentGuardian?
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
||||
|
||||
data class StudentGuardian(
|
||||
|
||||
@ColumnInfo(name = "full_name")
|
||||
val fullName: String,
|
||||
|
||||
val kinship: String,
|
||||
|
||||
val address: String,
|
||||
|
||||
val phones: String,
|
||||
|
||||
val email: String
|
||||
) : Serializable
|
@ -0,0 +1,20 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
data class StudentNickAndAvatar(
|
||||
|
||||
val nick: String,
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor: Long
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration31 : Migration(30, 31) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS StudentInfo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
student_id INTEGER NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
second_name TEXT NOT NULL,
|
||||
surname TEXT NOT NULL,
|
||||
birth_date INTEGER NOT NULL,
|
||||
birth_place TEXT NOT NULL,
|
||||
gender TEXT NOT NULL,
|
||||
has_polish_citizenship INTEGER NOT NULL,
|
||||
family_name TEXT NOT NULL,
|
||||
parents_names TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
registered_address TEXT NOT NULL,
|
||||
correspondence_address TEXT NOT NULL,
|
||||
phone_number TEXT NOT NULL,
|
||||
cell_phone_number TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
first_guardian_full_name TEXT NOT NULL,
|
||||
first_guardian_kinship TEXT NOT NULL,
|
||||
first_guardian_address TEXT NOT NULL,
|
||||
first_guardian_phones TEXT NOT NULL,
|
||||
first_guardian_email TEXT NOT NULL,
|
||||
second_guardian_full_name TEXT NOT NULL,
|
||||
second_guardian_kinship TEXT NOT NULL,
|
||||
second_guardian_address TEXT NOT NULL,
|
||||
second_guardian_phones TEXT NOT NULL,
|
||||
second_guardian_email TEXT NOT NULL)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration32 : Migration(31, 32) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration33 : Migration(32, 33) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DROP TABLE IF EXISTS StudentInfo")
|
||||
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS StudentInfo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
student_id INTEGER NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
second_name TEXT NOT NULL,
|
||||
surname TEXT NOT NULL,
|
||||
birth_date INTEGER NOT NULL,
|
||||
birth_place TEXT NOT NULL,
|
||||
gender TEXT NOT NULL,
|
||||
has_polish_citizenship INTEGER NOT NULL,
|
||||
family_name TEXT NOT NULL,
|
||||
parents_names TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
registered_address TEXT NOT NULL,
|
||||
correspondence_address TEXT NOT NULL,
|
||||
phone_number TEXT NOT NULL,
|
||||
cell_phone_number TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
first_guardian_full_name TEXT,
|
||||
first_guardian_kinship TEXT,
|
||||
first_guardian_address TEXT,
|
||||
first_guardian_phones TEXT,
|
||||
first_guardian_email TEXT,
|
||||
second_guardian_full_name TEXT,
|
||||
second_guardian_kinship TEXT,
|
||||
second_guardian_address TEXT,
|
||||
second_guardian_phones TEXT,
|
||||
second_guardian_email TEXT)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration34 : Migration(33, 34) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DELETE FROM ReportingUnits")
|
||||
database.execSQL("DELETE FROM Recipients")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
|
||||
class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
|
||||
|
||||
val studentsCursor = database.query("SELECT * FROM Students")
|
||||
|
||||
while (studentsCursor.moveToNext()) {
|
||||
val studentId = studentsCursor.getLongOrNull(0)
|
||||
database.execSQL(
|
||||
"""UPDATE Students
|
||||
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
|
||||
WHERE id = $studentId"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class Gender { MALE, FEMALE }
|
@ -5,10 +5,9 @@ import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester
|
||||
import io.github.wulkanowy.sdk.pojo.GradePointsStatistics as SdkGradePointsStatistics
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester
|
||||
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject
|
||||
|
||||
@JvmName("mapToEntitiesSubject")
|
||||
fun List<SdkGradeStatisticsSubject>.mapToEntities(semester: Semester) = map {
|
||||
@ -51,7 +50,7 @@ fun List<SdkGradePointsStatistics>.mapToEntities(semester: Semester) = map {
|
||||
|
||||
fun List<GradePartialStatistics>.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.PARTIAL,
|
||||
type = GradeStatisticsItem.DataType.PARTIAL,
|
||||
average = it.classAverage,
|
||||
partial = it,
|
||||
points = null,
|
||||
@ -61,7 +60,7 @@ fun List<GradePartialStatistics>.mapPartialToStatisticItems() = filterNot { it.c
|
||||
|
||||
fun List<GradeSemesterStatistics>.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.SEMESTER,
|
||||
type = GradeStatisticsItem.DataType.SEMESTER,
|
||||
partial = null,
|
||||
points = null,
|
||||
average = "",
|
||||
@ -71,7 +70,7 @@ fun List<GradeSemesterStatistics>.mapSemesterToStatisticItems() = filterNot { it
|
||||
|
||||
fun List<GradePointsStatistics>.mapPointsToStatisticsItems() = map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.POINTS,
|
||||
type = GradeStatisticsItem.DataType.POINTS,
|
||||
partial = null,
|
||||
semester = null,
|
||||
average = "",
|
||||
|
@ -4,14 +4,14 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||
import java.time.LocalDateTime
|
||||
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
|
||||
fun List<SdkMessage>.mapToEntities(student: Student) = map {
|
||||
Message(
|
||||
studentId = student.id.toInt(),
|
||||
studentId = student.id,
|
||||
realId = it.id ?: 0,
|
||||
messageId = it.messageId ?: 0,
|
||||
sender = it.sender?.name.orEmpty(),
|
||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
|
||||
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map {
|
||||
Recipient(
|
||||
userLoginId = userLoginId,
|
||||
studentId = userLoginId,
|
||||
realId = it.id,
|
||||
realName = it.name,
|
||||
name = it.shortName,
|
||||
|
@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
|
||||
|
||||
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
|
||||
ReportingUnit(
|
||||
studentId = student.studentId,
|
||||
studentId = student.id.toInt(),
|
||||
unitId = it.id,
|
||||
roles = it.roles,
|
||||
senderId = it.senderId,
|
||||
|
@ -0,0 +1,38 @@
|
||||
package io.github.wulkanowy.data.mappers
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.StudentGuardian
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
import io.github.wulkanowy.data.enums.Gender
|
||||
import io.github.wulkanowy.sdk.pojo.StudentGuardian as SdkStudentGuardian
|
||||
import io.github.wulkanowy.sdk.pojo.StudentInfo as SdkStudentInfo
|
||||
|
||||
fun SdkStudentInfo.mapToEntity(semester: Semester) = StudentInfo(
|
||||
studentId = semester.studentId,
|
||||
fullName = fullName,
|
||||
firstName = firstName,
|
||||
secondName = secondName,
|
||||
surname = surname,
|
||||
birthDate = birthDate,
|
||||
birthPlace = birthPlace,
|
||||
gender = Gender.valueOf(gender.name),
|
||||
hasPolishCitizenship = hasPolishCitizenship,
|
||||
familyName = familyName,
|
||||
parentsNames = parentsNames,
|
||||
address = address,
|
||||
registeredAddress = registeredAddress,
|
||||
correspondenceAddress = correspondenceAddress,
|
||||
phoneNumber = phoneNumber,
|
||||
cellPhoneNumber = phoneNumber,
|
||||
email = email,
|
||||
firstGuardian = guardianFirst?.mapToEntity(),
|
||||
secondGuardian = guardianSecond?.mapToEntity()
|
||||
)
|
||||
|
||||
fun SdkStudentGuardian.mapToEntity() = StudentGuardian(
|
||||
fullName = fullName,
|
||||
kinship = kinship,
|
||||
address = address,
|
||||
phones = phones,
|
||||
email = email
|
||||
)
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import java.time.LocalDateTime
|
||||
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
|
||||
|
||||
fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
||||
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
|
||||
StudentWithSemesters(
|
||||
student = Student(
|
||||
email = it.email,
|
||||
@ -28,8 +28,10 @@ fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
||||
mobileBaseUrl = it.mobileBaseUrl,
|
||||
privateKey = it.privateKey,
|
||||
certificateKey = it.certificateKey,
|
||||
loginMode = it.loginMode.name
|
||||
),
|
||||
loginMode = it.loginMode.name,
|
||||
).apply {
|
||||
avatarColor = colors.random()
|
||||
},
|
||||
semesters = it.semesters.mapToEntities(it.studentId)
|
||||
)
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ package io.github.wulkanowy.data.pojos
|
||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
|
||||
|
||||
data class GradeStatisticsItem(
|
||||
|
||||
val type: ViewType,
|
||||
val type: DataType,
|
||||
|
||||
val average: String,
|
||||
|
||||
@ -16,4 +15,11 @@ data class GradeStatisticsItem(
|
||||
val semester: GradeSemesterStatistics?,
|
||||
|
||||
val points: GradePointsStatistics?
|
||||
)
|
||||
|
||||
) {
|
||||
enum class DataType {
|
||||
SEMESTER,
|
||||
PARTIAL,
|
||||
POINTS,
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@ -27,9 +28,12 @@ class AttendanceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance_summary"
|
||||
|
||||
fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "completed"
|
||||
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class ConferenceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "conference"
|
||||
|
||||
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { conferenceDb.loadAll(semester.diaryId, student.studentId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.startExamsDay
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class ExamRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "exam"
|
||||
|
||||
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) },
|
||||
fetch = {
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,14 +29,20 @@ class GradeRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "grade"
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
gradeDb.loadAll(semester.semesterId, semester.studentId).combine(gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)) { details, summaries ->
|
||||
details to summaries
|
||||
}
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
|
||||
},
|
||||
fetch = {
|
||||
val (details, summary) = sdk.init(student)
|
||||
@ -92,19 +99,27 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isRead } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isRead }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isNotified } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedPredictedGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedFinalGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGrade(grade: Grade) {
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,11 +31,16 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val partialMutex = Mutex()
|
||||
private val semesterMutex = Mutex()
|
||||
private val pointsMutex = Mutex()
|
||||
|
||||
private val partialCacheKey = "grade_stats_partial"
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
private val pointsCacheKey = "grade_stats_points"
|
||||
|
||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = partialMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -71,6 +77,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = semesterMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -112,6 +119,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = pointsMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -24,9 +25,12 @@ class HomeworkRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -9,6 +9,8 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -19,7 +21,10 @@ class LuckyNumberRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||
@ -33,6 +38,9 @@ class LuckyNumberRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
|
@ -20,6 +20,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -33,10 +34,13 @@ class MessageRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "message"
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student, folder)) },
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) },
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class MobileDeviceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "devices"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class NoteRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "note"
|
||||
|
||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
@ -18,26 +19,43 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
||||
|
||||
val isShowPresent: Boolean
|
||||
get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_attendance_present,
|
||||
R.bool.pref_default_attendance_present
|
||||
)
|
||||
|
||||
val gradeAverageMode: GradeAverageMode
|
||||
get() = GradeAverageMode.getByValue(getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode))
|
||||
get() = GradeAverageMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_grade_average_mode,
|
||||
R.string.pref_default_grade_average_mode
|
||||
)
|
||||
)
|
||||
|
||||
val gradeAverageForceCalc: Boolean
|
||||
get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_grade_average_force_calc,
|
||||
R.bool.pref_default_grade_average_force_calc
|
||||
)
|
||||
|
||||
val isGradeExpandable: Boolean
|
||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||
|
||||
val showAllSubjectsOnStatisticsList: Boolean
|
||||
get() = getBoolean(R.string.pref_key_grade_statistics_list, R.bool.pref_default_grade_statistics_list)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_grade_statistics_list,
|
||||
R.bool.pref_default_grade_statistics_list
|
||||
)
|
||||
|
||||
val appThemeKey = context.getString(R.string.pref_key_app_theme)
|
||||
val appTheme: String
|
||||
get() = getString(appThemeKey, R.string.pref_default_app_theme)
|
||||
|
||||
val gradeColorTheme: String
|
||||
get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme)
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_color_scheme,
|
||||
R.string.pref_default_grade_color_scheme
|
||||
)
|
||||
|
||||
val appLanguageKey = context.getString(R.string.pref_key_app_language)
|
||||
val appLanguage
|
||||
@ -55,50 +73,90 @@ class PreferencesRepository @Inject constructor(
|
||||
val isServicesOnlyWifi: Boolean
|
||||
get() = getBoolean(servicesOnlyWifiKey, R.bool.pref_default_services_wifi_only)
|
||||
|
||||
val notificationsEnableKey = context.getString(R.string.pref_key_notifications_enable)
|
||||
val isNotificationsEnable: Boolean
|
||||
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
|
||||
get() = getBoolean(notificationsEnableKey, R.bool.pref_default_notifications_enable)
|
||||
|
||||
val isUpcomingLessonsNotificationsEnableKey = context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||
val isUpcomingLessonsNotificationsEnableKey =
|
||||
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||
val isUpcomingLessonsNotificationsEnable: Boolean
|
||||
get() = getBoolean(isUpcomingLessonsNotificationsEnableKey, R.bool.pref_default_notification_upcoming_lessons_enable)
|
||||
get() = getBoolean(
|
||||
isUpcomingLessonsNotificationsEnableKey,
|
||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||
)
|
||||
|
||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||
val isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
|
||||
val gradePlusModifier: Double
|
||||
get() = getString(R.string.pref_key_grade_modifier_plus, R.string.pref_default_grade_modifier_plus).toDouble()
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_modifier_plus,
|
||||
R.string.pref_default_grade_modifier_plus
|
||||
).toDouble()
|
||||
|
||||
val gradeMinusModifier: Double
|
||||
get() = getString(R.string.pref_key_grade_modifier_minus, R.string.pref_default_grade_modifier_minus).toDouble()
|
||||
get() = getString(
|
||||
R.string.pref_key_grade_modifier_minus,
|
||||
R.string.pref_default_grade_modifier_minus
|
||||
).toDouble()
|
||||
|
||||
val fillMessageContent: Boolean
|
||||
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_fill_message_content,
|
||||
R.bool.pref_default_fill_message_content
|
||||
)
|
||||
|
||||
val showGroupsInPlan: Boolean
|
||||
get() = getBoolean(R.string.pref_key_timetable_show_groups, R.bool.pref_default_timetable_show_groups)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_timetable_show_groups,
|
||||
R.bool.pref_default_timetable_show_groups
|
||||
)
|
||||
|
||||
val showWholeClassPlan: String
|
||||
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
|
||||
get() = getString(
|
||||
R.string.pref_key_timetable_show_whole_class,
|
||||
R.string.pref_default_timetable_show_whole_class
|
||||
)
|
||||
|
||||
val gradeSortingMode: GradeSortingMode
|
||||
get() = GradeSortingMode.getByValue(getString(R.string.pref_key_grade_sorting_mode, R.string.pref_default_grade_sorting_mode))
|
||||
get() = GradeSortingMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_grade_sorting_mode,
|
||||
R.string.pref_default_grade_sorting_mode
|
||||
)
|
||||
)
|
||||
|
||||
val showTimetableTimers: Boolean
|
||||
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_timetable_show_timers,
|
||||
R.bool.pref_default_timetable_show_timers
|
||||
)
|
||||
|
||||
var isHomeworkFullscreen: Boolean
|
||||
get() = getBoolean(R.string.pref_key_homework_fullscreen, R.bool.pref_default_homework_fullscreen)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_homework_fullscreen,
|
||||
R.bool.pref_default_homework_fullscreen
|
||||
)
|
||||
set(value) = sharedPref.edit().putBoolean("homework_fullscreen", value).apply()
|
||||
|
||||
val showSubjectsWithoutGrades: Boolean
|
||||
get() = getBoolean(R.string.pref_key_subjects_without_grades, R.bool.pref_default_subjects_without_grades)
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_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: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
||||
private fun getString(id: String, default: Int) =
|
||||
sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
||||
|
||||
private fun getBoolean(id: Int, default: Int) = getBoolean(context.getString(id), default)
|
||||
|
||||
private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
private fun getBoolean(id: String, default: Int) =
|
||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
}
|
||||
|
@ -19,22 +19,22 @@ class RecipientRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.senderId)
|
||||
val old = recipientDb.loadAll(unit.senderId, unit.unitId, role)
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
recipientDb.loadAll(unit.senderId, unit.unitId, role)
|
||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -18,25 +18,25 @@ class ReportingUnitRepository @Inject constructor(
|
||||
|
||||
suspend fun refreshReportingUnits(student: 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.insertAll(new.uniqueSubtract(old))
|
||||
}
|
||||
|
||||
suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
|
||||
return reportingUnitDb.load(student.studentId).ifEmpty {
|
||||
return reportingUnitDb.load(student.id.toInt()).ifEmpty {
|
||||
refreshReportingUnits(student)
|
||||
|
||||
reportingUnitDb.load(student.studentId)
|
||||
reportingUnitDb.load(student.id.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return reportingUnitDb.loadOne(student.studentId, unitId)
|
||||
return reportingUnitDb.loadOne(student.id.toInt(), unitId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,16 +17,26 @@ class SchoolRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = { sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool().mapToEntity(semester) },
|
||||
saveFetchResult = { old, new ->
|
||||
if (new != old && old != null) {
|
||||
schoolDb.deleteAll(listOf(old))
|
||||
schoolDb.insertAll(listOf(new))
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
}
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class StudentInfoRepository @Inject constructor(
|
||||
private val studentInfoDao: StudentInfoDao,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -5,10 +5,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.security.decrypt
|
||||
import io.github.wulkanowy.utils.security.encrypt
|
||||
@ -22,54 +24,89 @@ class StudentRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty()
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
|
||||
suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false
|
||||
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
|
||||
|
||||
suspend fun getStudentsApi(pin: String, symbol: String, token: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
}
|
||||
suspend fun getStudentsApi(
|
||||
pin: String,
|
||||
symbol: String,
|
||||
token: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "")
|
||||
.mapToEntities(colors = appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsScrapper(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> {
|
||||
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadStudentsWithSemesters().map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password)
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
||||
studentDb.loadStudentsWithSemesters()
|
||||
.map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadById(id)?.apply {
|
||||
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password)
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadCurrent()?.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password)
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||
semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters })
|
||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||
val students = studentsWithSemesters.map { it.student }
|
||||
.map {
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
password = withContext(dispatchers.backgroundThread) {
|
||||
encrypt(password, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context))
|
||||
else it
|
||||
})
|
||||
}
|
||||
semesterDb.insertSemesters(semesters)
|
||||
return studentDb.insertAll(students)
|
||||
}
|
||||
|
||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||
@ -79,7 +116,8 @@ class StudentRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun logoutStudent(student: Student) {
|
||||
studentDb.delete(student)
|
||||
}
|
||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||
|
||||
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
|
||||
studentDb.update(studentNickAndAvatar)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class SubjectRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class TeacherRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -31,9 +32,12 @@ class TimetableRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = {
|
||||
timetableDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
|
@ -27,6 +27,7 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
private val dispatchersProvider: DispatchersProvider,
|
||||
) {
|
||||
|
||||
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
|
||||
private fun getRequestCode(time: LocalDateTime, studentId: Int) =
|
||||
(time.toTimestamp() * studentId).toInt()
|
||||
|
||||
private fun getUpcomingLessonTime(index: Int, day: List<Timetable>, lesson: Timetable): LocalDateTime {
|
||||
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||
}
|
||||
private fun getUpcomingLessonTime(
|
||||
index: Int,
|
||||
day: List<Timetable>,
|
||||
lesson: Timetable
|
||||
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||
|
||||
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
|
||||
cancelScheduledTo(
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
)
|
||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||
|
||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||
@ -61,13 +68,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||
if (now() in range) cancelNotification()
|
||||
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT))
|
||||
alarmManager.cancel(
|
||||
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
fun cancelNotification() =
|
||||
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
|
||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||
return cancelScheduled(lessons, student.studentId)
|
||||
}
|
||||
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.groupBy { it.date }
|
||||
@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
val intent = createIntent(student, lesson, active.getOrNull(index + 1))
|
||||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson))
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,7 +126,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
|
||||
return Intent(context, TimetableNotificationReceiver::class.java).apply {
|
||||
putExtra(STUDENT_ID, student.studentId)
|
||||
putExtra(STUDENT_NAME, student.studentName)
|
||||
putExtra(STUDENT_NAME, student.nickOrName)
|
||||
putExtra(LESSON_ROOM, lesson.room)
|
||||
putExtra(LESSON_START, lesson.start.toTimestamp())
|
||||
putExtra(LESSON_END, lesson.end.toTimestamp())
|
||||
@ -109,13 +136,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
private fun scheduleBroadcast(
|
||||
intent: Intent,
|
||||
studentId: Int,
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.BigTextStyle
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.hilt.Assisted
|
||||
import androidx.hilt.work.WorkerInject
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
@ -23,7 +24,8 @@ import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import kotlin.random.Random
|
||||
|
||||
class SyncWorker @WorkerInject constructor(
|
||||
@HiltWorker
|
||||
class SyncWorker @AssistedInject constructor(
|
||||
@Assisted appContext: Context,
|
||||
@Assisted workerParameters: WorkerParameters,
|
||||
private val studentRepository: StudentRepository,
|
||||
@ -58,9 +60,10 @@ class SyncWorker @WorkerInject constructor(
|
||||
}
|
||||
val result = when {
|
||||
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
|
||||
Result.failure(Data.Builder()
|
||||
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
|
||||
.build()
|
||||
Result.failure(
|
||||
Data.Builder()
|
||||
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
exceptions.isNotEmpty() -> Result.retry()
|
||||
@ -74,13 +77,16 @@ class SyncWorker @WorkerInject constructor(
|
||||
}
|
||||
|
||||
private fun notify(result: Result) {
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
|
||||
.setContentTitle("Debug notification")
|
||||
.setSmallIcon(R.drawable.ic_stat_push)
|
||||
.setAutoCancel(true)
|
||||
.setColor(applicationContext.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
|
||||
.setPriority(PRIORITY_DEFAULT)
|
||||
.build())
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
|
||||
.setContentTitle("Debug notification")
|
||||
.setSmallIcon(R.drawable.ic_stat_push)
|
||||
.setAutoCancel(true)
|
||||
.setColor(applicationContext.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
|
||||
.setPriority(PRIORITY_DEFAULT)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
abstract var presenter: T
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
inject()
|
||||
themeManager.applyActivityTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
@ -44,7 +45,9 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
|
||||
if (SDK_INT >= LOLLIPOP) {
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)))
|
||||
setTaskDescription(
|
||||
ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,4 +87,9 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
invalidateOptionsMenu()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
|
||||
//https://github.com/google/dagger/releases/tag/dagger-2.33
|
||||
protected open fun inject() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
||||
//TODO Use ViewPager2
|
||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
|
@ -42,10 +42,8 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
companion object {
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(error: Throwable): ErrorDialog {
|
||||
return ErrorDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) }
|
||||
}
|
||||
fun newInstance(error: Throwable) = ErrorDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,12 +55,14 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val stringWriter = StringWriter().apply {
|
||||
error.printStackTrace(PrintWriter(this))
|
||||
@ -114,11 +114,17 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = "Zgłoszenie błędu",
|
||||
body = requireContext().getString(R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
|
||||
body = requireContext().getString(
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
) + "\n" + content,
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
|
||||
requireContext().openInternetBrowser(
|
||||
"https://github.com/wulkanowy/wulkanowy/issues",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +20,13 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
fun applyActivityTheme(activity: AppCompatActivity) {
|
||||
if (isThemeApplicable(activity)) {
|
||||
applyDefaultTheme()
|
||||
if (preferencesRepository.appTheme == "black") activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
if (preferencesRepository.appTheme == "black") {
|
||||
when (activity) {
|
||||
is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black)
|
||||
is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black)
|
||||
is SendMessageActivity -> activity.setTheme(R.style.WulkanowyTheme_MessageSend_Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,8 +42,13 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||
return activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }?.theme
|
||||
.let { it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar }
|
||||
return activity.packageManager
|
||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme.let {
|
||||
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
||||
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
||||
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,25 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<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 = {}
|
||||
|
||||
@ -25,17 +31,33 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<Widget
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
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) {
|
||||
accountItemName.text = "${student.studentName} ${student.className}"
|
||||
accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = student.schoolName
|
||||
accountItemImage.setImageDrawable(avatar)
|
||||
|
||||
with(accountItemImage) {
|
||||
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
with(accountItemAccountType) {
|
||||
setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
isVisible = isDuplicatedStudent
|
||||
}
|
||||
|
||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
with(accountItemCheck) {
|
||||
isVisible = student.id == selectedId
|
||||
borderColor = checkBackgroundColor
|
||||
circleColor = checkBackgroundColor
|
||||
}
|
||||
|
||||
root.setOnClickListener { onClickListener(student) }
|
||||
|
@ -18,6 +18,8 @@ import io.github.wulkanowy.utils.getCompatDrawable
|
||||
import io.github.wulkanowy.utils.openAppInMarket
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -35,7 +37,9 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
|
||||
override val versionRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
|
||||
val buildTimestamp = appInfo.buildTimestamp.toLocalDateTime().toFormattedString("yyyy-MM-dd")
|
||||
val versionSignature = "${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp"
|
||||
Triple(getString(R.string.about_version), versionSignature, getCompatDrawable(R.drawable.ic_all_about))
|
||||
}
|
||||
|
||||
override val creatorsRes: Triple<String, String, Drawable?>?
|
||||
@ -65,7 +69,11 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
|
||||
override val homepageRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage))
|
||||
Triple(
|
||||
getString(R.string.about_homepage),
|
||||
getString(R.string.about_homepage_summary),
|
||||
getCompatDrawable(R.drawable.ic_all_home)
|
||||
)
|
||||
}
|
||||
|
||||
override val licensesRes: Triple<String, String, Drawable?>?
|
||||
@ -131,11 +139,17 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = "Zgłoszenie błędu",
|
||||
body = getString(R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
|
||||
body = getString(
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
),
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
|
||||
requireContext().openInternetBrowser(
|
||||
"https://github.com/wulkanowy/wulkanowy/issues",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -26,14 +26,9 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
|
||||
@Inject
|
||||
lateinit var licenseAdapter: LicenseAdapter
|
||||
|
||||
private val libs by lazy { Libs(requireContext()) }
|
||||
|
||||
override val titleStringId get() = R.string.license_title
|
||||
|
||||
override val appLibraries: ArrayList<Library>?
|
||||
get() = context?.let {
|
||||
libs.prepareLibraries(it, emptyArray(), emptyArray(), autoDetect = true, checkCachedDetection = true, sort = true)
|
||||
}
|
||||
override val appLibraries by lazy { Libs(requireContext()).libraries }
|
||||
|
||||
companion object {
|
||||
fun newInstance() = LicenseFragment()
|
||||
@ -63,7 +58,7 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
|
||||
|
||||
override fun openLicense(licenseHtml: String) {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it).apply {
|
||||
MaterialAlertDialogBuilder(it).apply {
|
||||
setTitle(R.string.license_dialog_title)
|
||||
setMessage(licenseHtml.parseAsHtml())
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface LicenseView : BaseView {
|
||||
|
||||
val appLibraries: ArrayList<Library>?
|
||||
val appLibraries: List<Library>
|
||||
|
||||
fun initView()
|
||||
|
||||
|
@ -1,23 +1,25 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
var isAccountQuickDialogMode = false
|
||||
|
||||
var items = emptyList<AccountItem<*>>()
|
||||
|
||||
var onClickListener: (StudentWithSemesters) -> Unit = {}
|
||||
@ -30,56 +32,76 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
|
||||
return when (viewType) {
|
||||
AccountItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderAccountBinding.inflate(inflater, parent, false))
|
||||
AccountItem.ViewType.ITEM.id -> ItemViewHolder(ItemAccountBinding.inflate(inflater, parent, false))
|
||||
AccountItem.ViewType.HEADER.id -> HeaderViewHolder(
|
||||
HeaderAccountBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
AccountItem.ViewType.ITEM.id -> ItemViewHolder(
|
||||
ItemAccountBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as Account)
|
||||
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as StudentWithSemesters)
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(
|
||||
holder.binding,
|
||||
items[position].value as Account,
|
||||
position
|
||||
)
|
||||
is ItemViewHolder -> bindItemViewHolder(
|
||||
holder.binding,
|
||||
items[position].value as StudentWithSemesters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) {
|
||||
private fun bindHeaderViewHolder(
|
||||
binding: HeaderAccountBinding,
|
||||
account: Account,
|
||||
position: Int
|
||||
) {
|
||||
with(binding) {
|
||||
accountHeaderDivider.visibility = if (position == 0) GONE else VISIBLE
|
||||
accountHeaderEmail.text = account.email
|
||||
accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindItemViewHolder(binding: ItemAccountBinding, studentWithSemesters: StudentWithSemesters) {
|
||||
val student = studentWithSemesters.student
|
||||
val semesters = studentWithSemesters.semesters
|
||||
val diary = semesters.maxByOrNull { it.semesterId }
|
||||
private fun bindItemViewHolder(
|
||||
binding: ItemAccountBinding,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
val context = binding.root.context
|
||||
val (student, semesters) = studentWithSemesters
|
||||
val semester = semesters.maxByOrNull { it.semesterId }
|
||||
val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor)
|
||||
val checkBackgroundColor =
|
||||
context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface)
|
||||
val isDuplicatedStudent = items.filter {
|
||||
if (it.value !is StudentWithSemesters) return@filter false
|
||||
val studentToCompare = it.value.student
|
||||
|
||||
studentToCompare.studentId == student.studentId
|
||||
&& studentToCompare.schoolSymbol == student.schoolSymbol
|
||||
&& studentToCompare.symbol == student.symbol
|
||||
}.size > 1 && isAccountQuickDialogMode
|
||||
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemName.text = "${student.nickOrName} ${semester?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||
with(accountItemLoginMode) {
|
||||
visibility = when (Sdk.Mode.valueOf(student.loginMode)) {
|
||||
Sdk.Mode.API -> {
|
||||
setText(R.string.account_login_mobile_api)
|
||||
VISIBLE
|
||||
}
|
||||
Sdk.Mode.HYBRID -> {
|
||||
setText(R.string.account_login_hybrid)
|
||||
VISIBLE
|
||||
}
|
||||
Sdk.Mode.SCRAPPER -> {
|
||||
GONE
|
||||
}
|
||||
}
|
||||
accountItemImage.setImageDrawable(avatar)
|
||||
|
||||
with(accountItemAccountType) {
|
||||
setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
isVisible = isDuplicatedStudent
|
||||
}
|
||||
|
||||
with(accountItemImage) {
|
||||
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
|
||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
with(accountItemCheck) {
|
||||
isVisible = student.isCurrent
|
||||
borderColor = checkBackgroundColor
|
||||
circleColor = checkBackgroundColor
|
||||
}
|
||||
|
||||
root.setOnClickListener { onClickListener(studentWithSemesters) }
|
||||
|
@ -1,102 +0,0 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountDialog : BaseDialogFragment<DialogAccountBinding>(), AccountView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = AccountDialog()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogAccountBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
|
||||
accountDialogRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
showMessage(text)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
Toast.makeText(context, text, LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun dismissView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.let {
|
||||
startActivity(LoginActivity.getStartIntent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showConfirmDialog() {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.account_logout_student)
|
||||
.setMessage(R.string.account_confirm)
|
||||
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import androidx.core.view.get
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.FragmentAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_account),
|
||||
AccountView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = AccountFragment()
|
||||
}
|
||||
|
||||
override val titleStringId = R.string.account_title
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding = FragmentAccountBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
}
|
||||
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
binding.accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openLoginView() {
|
||||
activity?.let {
|
||||
startActivity(LoginActivity.getStartIntent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
AccountDetailsFragment.newInstance(studentWithSemesters)
|
||||
)
|
||||
}
|
||||
}
|
@ -3,10 +3,8 @@ package io.github.wulkanowy.ui.modules.account
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
@ -15,13 +13,12 @@ import javax.inject.Inject
|
||||
class AccountPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: AccountView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Account dialog view was initialized")
|
||||
Timber.i("Account view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
@ -30,86 +27,39 @@ class AccountPresenter @Inject constructor(
|
||||
view?.openLoginView()
|
||||
}
|
||||
|
||||
fun onRemoveSelected() {
|
||||
Timber.i("Select remove account")
|
||||
view?.showConfirmDialog()
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
flowWithResource {
|
||||
val student = studentRepository.getCurrentStudent(false)
|
||||
studentRepository.logoutStudent(student)
|
||||
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
if (students.isNotEmpty()) {
|
||||
studentRepository.switchStudent(students[0])
|
||||
}
|
||||
students
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to logout current user ")
|
||||
Status.SUCCESS -> view?.run {
|
||||
if (it.data!!.isEmpty()) {
|
||||
Timber.i("Logout result: Open login view")
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
} else {
|
||||
Timber.i("Logout result: Switch to another student")
|
||||
recreateMainView()
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Logout result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.dismissView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
|
||||
Timber.i("Select student item ${studentWithSemesters.student.id}")
|
||||
if (studentWithSemesters.student.isCurrent) {
|
||||
view?.dismissView()
|
||||
} else flowWithResource { studentRepository.switchStudent(studentWithSemesters) }.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.dismissView()
|
||||
}.launch("switch")
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
||||
return items.groupBy { Account(it.student.email, it.student.isParent) }.map { (account, students) ->
|
||||
listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student ->
|
||||
AccountItem(student, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
}.flatten()
|
||||
view?.openAccountDetailsView(studentWithSemesters)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading account data started")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading account data started")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.launch()
|
||||
.launch("load")
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
||||
return items.groupBy {
|
||||
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
||||
}
|
||||
.map { (account, students) ->
|
||||
listOf(
|
||||
AccountItem(account, AccountItem.ViewType.HEADER)
|
||||
) + students.map { student ->
|
||||
AccountItem(student, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
@ -8,12 +9,7 @@ interface AccountView : BaseView {
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
|
||||
fun dismissView()
|
||||
|
||||
fun showConfirmDialog()
|
||||
|
||||
fun openLoginView()
|
||||
|
||||
fun recreateMainView()
|
||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,164 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountDetailsFragment :
|
||||
BaseFragment<FragmentAccountDetailsBinding>(R.layout.fragment_account_details),
|
||||
AccountDetailsView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountDetailsPresenter
|
||||
|
||||
override val titleStringId = R.string.account_details_title
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(studentWithSemesters: StudentWithSemesters) =
|
||||
AccountDetailsFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() }
|
||||
binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() }
|
||||
|
||||
binding.accountDetailsPersonalData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL)
|
||||
}
|
||||
binding.accountDetailsAddressData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.ADDRESS)
|
||||
}
|
||||
binding.accountDetailsContactData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.CONTACT)
|
||||
}
|
||||
binding.accountDetailsFamilyData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.FAMILY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
inflater.inflate(R.menu.action_menu_account_details, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.accountDetailsMenuEdit) {
|
||||
presenter.onAccountEditSelected()
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun showAccountData(student: Student) {
|
||||
with(binding) {
|
||||
accountDetailsCheck.isVisible = student.isCurrent
|
||||
accountDetailsName.text = student.nickOrName
|
||||
accountDetailsSchool.text = student.schoolName
|
||||
accountDetailsAvatar.setImageDrawable(
|
||||
requireContext().createNameInitialsDrawable(
|
||||
student.nickOrName,
|
||||
student.avatarColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableSelectStudentButton(enable: Boolean) {
|
||||
binding.accountDetailsSelect.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showAccountEditDetailsDialog(student: Student) {
|
||||
(requireActivity() as MainActivity).showDialogFragment(
|
||||
AccountEditDialog.newInstance(student)
|
||||
)
|
||||
}
|
||||
|
||||
override fun showLogoutConfirmDialog() {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.account_logout_student)
|
||||
.setMessage(R.string.account_confirm)
|
||||
.setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
(requireActivity() as MainActivity).popView(2)
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
override fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
(requireActivity() as MainActivity).pushView(
|
||||
StudentInfoFragment.newInstance(
|
||||
infoType,
|
||||
studentWithSemesters
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.accountDetailsErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountDetailsPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||
|
||||
private var studentWithSemesters: StudentWithSemesters? = null
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
private var studentId: Long? = null
|
||||
|
||||
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
|
||||
super.onAttachView(view)
|
||||
studentId = studentWithSemesters.student.id
|
||||
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
Timber.i("Account details view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents() }
|
||||
.map { studentWithSemesters ->
|
||||
Resource(
|
||||
data = studentWithSemesters.data?.single { it.student.id == studentId },
|
||||
status = studentWithSemesters.status,
|
||||
error = studentWithSemesters.error
|
||||
)
|
||||
}
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
Timber.i("Loading account details view started")
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account details view result: Success")
|
||||
studentWithSemesters = it.data
|
||||
view?.run {
|
||||
showAccountData(studentWithSemesters!!.student)
|
||||
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading account details view result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.showProgress(false) }
|
||||
.launch()
|
||||
}
|
||||
|
||||
fun onAccountEditSelected() {
|
||||
studentWithSemesters?.let {
|
||||
view?.showAccountEditDetailsDialog(it.student)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
||||
studentWithSemesters?.let {
|
||||
view?.openStudentInfoView(infoType, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentSelect() {
|
||||
if (studentWithSemesters == null) return
|
||||
|
||||
Timber.i("Select student ${studentWithSemesters!!.student.id}")
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
}.launch("switch")
|
||||
}
|
||||
|
||||
fun onRemoveSelected() {
|
||||
Timber.i("Select remove account")
|
||||
view?.showLogoutConfirmDialog()
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
if (studentWithSemesters == null) return
|
||||
|
||||
flowWithResource {
|
||||
val studentToLogout = studentWithSemesters!!.student
|
||||
|
||||
studentRepository.logoutStudent(studentToLogout)
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
|
||||
if (studentToLogout.isCurrent && students.isNotEmpty()) {
|
||||
studentRepository.switchStudent(students[0])
|
||||
}
|
||||
|
||||
return@flowWithResource students
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to logout user")
|
||||
Status.SUCCESS -> view?.run {
|
||||
when {
|
||||
it.data!!.isEmpty() -> {
|
||||
Timber.i("Logout result: Open login view")
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters!!.student.isCurrent -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
else -> Timber.i("Logout result: Logout student")
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Logout result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
|
||||
interface AccountDetailsView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun showAccountData(student: Student)
|
||||
|
||||
fun showAccountEditDetailsDialog(student: Student)
|
||||
|
||||
fun showLogoutConfirmDialog()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun enableSelectStudentButton(enable: Boolean)
|
||||
|
||||
fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.databinding.ItemAccountEditColorBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditColorAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<AccountEditColorAdapter.ViewHolder>() {
|
||||
|
||||
var items = listOf<Int>()
|
||||
|
||||
var selectedColor = 0
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAccountEditColorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
@SuppressLint("RestrictedApi", "NewApi")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
accountEditItemColor.setImageDrawable(GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(item)
|
||||
})
|
||||
|
||||
accountEditItemColorContainer.foreground = item.createForegroundDrawable()
|
||||
accountEditCheck.isVisible = selectedColor == item
|
||||
|
||||
root.setOnClickListener {
|
||||
val oldSelectedPosition = items.indexOf(selectedColor)
|
||||
selectedColor = item
|
||||
|
||||
notifyItemChanged(oldSelectedPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.createForegroundDrawable(): Drawable =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val mask = GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(Color.BLACK)
|
||||
}
|
||||
RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
|
||||
} else {
|
||||
val foreground = StateListDrawable().apply {
|
||||
alpha = 80
|
||||
setEnterFadeDuration(250)
|
||||
setExitFadeDuration(250)
|
||||
}
|
||||
|
||||
val mask = GradientDrawable().apply {
|
||||
shape = GradientDrawable.OVAL
|
||||
setColor(this@createForegroundDrawable.rippleColor)
|
||||
}
|
||||
|
||||
foreground.apply {
|
||||
addState(intArrayOf(android.R.attr.state_pressed), mask)
|
||||
addState(intArrayOf(), ColorDrawable(Color.TRANSPARENT))
|
||||
}
|
||||
}
|
||||
|
||||
private inline val Int.rippleColor: Int
|
||||
get() {
|
||||
val hsv = FloatArray(3)
|
||||
Color.colorToHSV(this, hsv)
|
||||
hsv[2] = hsv[2] * 0.5f
|
||||
return Color.HSVToColor(hsv)
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: ItemAccountEditColorBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), AccountEditView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountEditPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountEditColorAdapter: AccountEditColorAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||
|
||||
fun newInstance(student: Student) =
|
||||
AccountEditDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_KEY, student)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(binding) {
|
||||
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
||||
accountEditDetailsSave.setOnClickListener {
|
||||
presenter.changeStudentNickAndAvatar(
|
||||
binding.accountEditDetailsNickText.text.toString(),
|
||||
accountEditColorAdapter.selectedColor
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.accountEditColors) {
|
||||
layoutManager = GridLayoutManager(context, 4)
|
||||
adapter = accountEditColorAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSelectedColorData(color: Int) {
|
||||
with(accountEditColorAdapter) {
|
||||
selectedColor = color
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateColorsData(colors: List<Int>) {
|
||||
with(accountEditColorAdapter) {
|
||||
items = colors
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showCurrentNick(nick: String) {
|
||||
binding.accountEditDetailsNickText.setText(nick)
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditPresenter @Inject constructor(
|
||||
private val appInfo: AppInfo,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
||||
|
||||
lateinit var student: Student
|
||||
|
||||
private val colors = appInfo.defaultColorsForAvatar.map { it.toInt() }
|
||||
|
||||
fun onAttachView(view: AccountEditView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
this.student = student
|
||||
|
||||
with(view) {
|
||||
initView()
|
||||
showCurrentNick(student.nick.trim())
|
||||
}
|
||||
Timber.i("Account edit dialog view was initialized")
|
||||
loadData()
|
||||
|
||||
view.updateColorsData(colors)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource {
|
||||
studentRepository.getStudentById(student.id, false).avatarColor
|
||||
}.onEach { resource ->
|
||||
when (resource.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to load student")
|
||||
Status.SUCCESS -> {
|
||||
view?.updateSelectedColorData(resource.data?.toInt()!!)
|
||||
Timber.i("Attempt to load student: Success")
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Attempt to load student: An exception occurred")
|
||||
errorHandler.dispatch(resource.error!!)
|
||||
}
|
||||
}
|
||||
}.launch("load_data")
|
||||
}
|
||||
|
||||
fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) {
|
||||
flowWithResource {
|
||||
val studentNick =
|
||||
StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong())
|
||||
.apply { id = student.id }
|
||||
studentRepository.updateStudentNickAndAvatar(studentNick)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student nick and avatar")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student nick and avatar result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student nick and avatar result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch("update_student")
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountEditView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun showCurrentNick(nick: String)
|
||||
|
||||
fun updateSelectedColorData(color: Int)
|
||||
|
||||
fun updateColorsData(colors: List<Int>)
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), AccountQuickView {
|
||||
|
||||
@Inject
|
||||
lateinit var accountAdapter: AccountAdapter
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AccountQuickPresenter
|
||||
|
||||
companion object {
|
||||
|
||||
private const val STUDENTS_ARGUMENT_KEY = "students"
|
||||
|
||||
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
|
||||
AccountQuickDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val studentsWithSemesters =
|
||||
(requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList()
|
||||
|
||||
presenter.onAttachView(this, studentsWithSemesters)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountQuickDialogManger.setOnClickListener { presenter.onManagerSelected() }
|
||||
|
||||
with(accountAdapter) {
|
||||
isAccountQuickDialogMode = true
|
||||
onClickListener = presenter::onStudentSelect
|
||||
}
|
||||
|
||||
with(binding.accountQuickDialogRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<AccountItem<*>>) {
|
||||
with(accountAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
override fun openAccountView() {
|
||||
(requireActivity() as MainActivity).pushView(AccountFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountQuickPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountQuickView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var studentsWithSemesters: List<StudentWithSemesters>
|
||||
|
||||
fun onAttachView(view: AccountQuickView, studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
super.onAttachView(view)
|
||||
this.studentsWithSemesters = studentsWithSemesters
|
||||
|
||||
view.initView()
|
||||
Timber.i("Account quick dialog view was initialized")
|
||||
view.updateData(createAccountItems(studentsWithSemesters))
|
||||
}
|
||||
|
||||
fun onManagerSelected() {
|
||||
view?.run {
|
||||
openAccountView()
|
||||
popView()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentSelect(studentWithSemesters: StudentWithSemesters) {
|
||||
Timber.i("Select student ${studentWithSemesters.student.id}")
|
||||
|
||||
if (studentWithSemesters.student.isCurrent) {
|
||||
view?.popView()
|
||||
return
|
||||
}
|
||||
|
||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Change a student result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Change a student result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch("switch")
|
||||
}
|
||||
|
||||
private fun createAccountItems(items: List<StudentWithSemesters>) = items.map {
|
||||
AccountItem(it, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.github.wulkanowy.ui.modules.account.accountquick
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
|
||||
interface AccountQuickView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun popView()
|
||||
|
||||
fun openAccountView()
|
||||
}
|
@ -18,12 +18,11 @@ class AttendanceDialog : DialogFragment() {
|
||||
private lateinit var attendance: Attendance
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Attendance): AttendanceDialog {
|
||||
return AttendanceDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
fun newInstance(exam: Attendance) = AttendanceDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,12 +34,14 @@ class AttendanceDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
attendanceDialogSubject.text = attendance.subject
|
||||
|
@ -26,6 +26,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -60,6 +61,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode
|
||||
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val inflater = mode.menuInflater
|
||||
@ -111,6 +113,8 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
|
||||
with(binding) {
|
||||
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
attendanceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
@ -188,7 +192,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -222,6 +226,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
vibrate(false)
|
||||
show(this@AttendanceFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
@ -190,35 +190,48 @@ class AttendancePresenter @Inject constructor(
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
attendanceRepository.getAttendance(student, semester, currentDate, currentDate, forceRefresh)
|
||||
attendanceRepository.getAttendance(
|
||||
student,
|
||||
semester,
|
||||
currentDate,
|
||||
currentDate,
|
||||
forceRefresh
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
view?.showExcuseButton(false)
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
val filteredAttendance = if (prefRepository.isShowPresent) {
|
||||
it.data
|
||||
} else {
|
||||
it.data.filter { item -> !item.presence }
|
||||
}
|
||||
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
updateData(it.data.let { items ->
|
||||
if (prefRepository.isShowPresent) items
|
||||
else items.filter { item -> !item.presence }
|
||||
}.sortedBy { item -> item.number })
|
||||
showEmpty(filteredAttendance.isEmpty())
|
||||
showContent(filteredAttendance.isNotEmpty())
|
||||
updateData(filteredAttendance.sortedBy { item -> item.number })
|
||||
}
|
||||
}
|
||||
}
|
||||
Status.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 {
|
||||
updateData(it.data!!.let { items ->
|
||||
if (prefRepository.isShowPresent) items
|
||||
else items.filter { item -> !item.presence }
|
||||
}.sortedBy { item -> item.number })
|
||||
showEmpty(it.data.isEmpty())
|
||||
updateData(filteredAttendance.sortedBy { item -> item.number })
|
||||
showEmpty(filteredAttendance.isEmpty())
|
||||
showErrorView(false)
|
||||
showContent(it.data.isNotEmpty())
|
||||
showExcuseButton(it.data.any { item -> item.excusable })
|
||||
showContent(filteredAttendance.isNotEmpty())
|
||||
showExcuseButton(filteredAttendance.any { item -> item.excusable })
|
||||
}
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -56,6 +57,8 @@ class AttendanceSummaryFragment :
|
||||
|
||||
with(binding) {
|
||||
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
attendanceSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
attendanceSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -47,7 +48,9 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
conferenceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
conferenceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
|
@ -17,12 +17,11 @@ class ExamDialog : DialogFragment() {
|
||||
private lateinit var exam: Exam
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Exam): ExamDialog {
|
||||
return ExamDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
fun newInstance(exam: Exam) = ExamDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,12 +33,14 @@ class ExamDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return DialogExamBinding.inflate(inflater).apply { binding = this }.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogExamBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
examDialogSubjectValue.text = exam.subject
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user