Compare commits
5 commits
dependabot
...
feature/at
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1591d494ab | ||
![]() |
1af44cf60a | ||
![]() |
28c234a8fd | ||
![]() |
f8c9122686 | ||
![]() |
7effb7aca2 |
240 changed files with 1865 additions and 16520 deletions
|
@ -162,7 +162,7 @@ jobs:
|
||||||
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
|
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
|
||||||
- run:
|
- run:
|
||||||
name: Publish release
|
name: Publish release
|
||||||
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
|
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
|
|
2
.github/workflows/deploy-store.yml
vendored
2
.github/workflows/deploy-store.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
||||||
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
||||||
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
|
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
|
||||||
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||||
run: ./gradlew publishPlayReleaseApps --stacktrace;
|
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: AppGallery
|
name: AppGallery
|
||||||
|
|
5
.github/workflows/deploy-test.yml
vendored
5
.github/workflows/deploy-test.yml
vendored
|
@ -36,7 +36,8 @@ jobs:
|
||||||
- name: Prepare build configuration
|
- name: Prepare build configuration
|
||||||
run: |
|
run: |
|
||||||
sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle
|
sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle
|
||||||
sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/google-services.json
|
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
|
sed -i -e '/versionNameSuffix/d' app/build.gradle
|
||||||
- name: Add signing config
|
- name: Add signing config
|
||||||
run: |
|
run: |
|
||||||
|
@ -130,7 +131,7 @@ jobs:
|
||||||
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
|
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
|
||||||
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
|
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
|
||||||
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
||||||
run: ./gradlew assemblePlayDebug --stacktrace
|
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
|
||||||
- name: Upload apk to github artifacts
|
- name: Upload apk to github artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -67,11 +67,6 @@ captures/
|
||||||
.idea/discord.xml
|
.idea/discord.xml
|
||||||
.idea/migrations.xml
|
.idea/migrations.xml
|
||||||
.idea/androidTestResultsUserPreferences.xml
|
.idea/androidTestResultsUserPreferences.xml
|
||||||
.idea/copilot
|
|
||||||
.idea/deploymentTargetDropDown.xml
|
|
||||||
.idea/deploymentTargetSelector.xml
|
|
||||||
.idea/kotlinc.xml
|
|
||||||
.idea/studiobot.xml
|
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
|
@ -118,14 +113,12 @@ Thumbs.db
|
||||||
*.ear
|
*.ear
|
||||||
|
|
||||||
### AndroidStudio Patch ###
|
### AndroidStudio Patch ###
|
||||||
|
|
||||||
!/gradle/wrapper/gradle-wrapper.jar
|
!/gradle/wrapper/gradle-wrapper.jar
|
||||||
.idea/jarRepositories.xml
|
.idea/jarRepositories.xml
|
||||||
|
|
||||||
### Services config files
|
|
||||||
agconnect-services.json
|
|
||||||
agconnect-credentials.json
|
|
||||||
google-services.json
|
|
||||||
!app/google-services.json
|
|
||||||
|
|
||||||
|
app/src/release/agconnect-services.json
|
||||||
.idea/appInsightsSettings.xml
|
app/src/release/agconnect-credentials.json
|
||||||
|
.idea/deploymentTargetDropDown.xml
|
||||||
|
.idea/kotlinc.xml
|
||||||
|
|
|
@ -61,7 +61,7 @@ script:
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
|
||||||
./gradlew publishPlayRelease --stacktrace;
|
./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -27,12 +27,15 @@ android {
|
||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 177
|
versionCode 148
|
||||||
versionName "2.7.0"
|
versionName "2.4.2"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
manifestPlaceholders = [admob_project_id: ""]
|
manifestPlaceholders = [
|
||||||
|
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||||
|
admob_project_id: ""
|
||||||
|
]
|
||||||
|
|
||||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
||||||
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
|
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
|
||||||
|
@ -73,6 +76,7 @@ android {
|
||||||
resValue "string", "app_name", "Wulkanowy DEV"
|
resValue "string", "app_name", "Wulkanowy DEV"
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
|
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||||
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
||||||
}
|
}
|
||||||
|
@ -186,30 +190,28 @@ ext {
|
||||||
android_hilt = "1.2.0"
|
android_hilt = "1.2.0"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
chucker = "4.0.0"
|
chucker = "4.0.0"
|
||||||
mockk = "1.13.11"
|
mockk = "1.13.9"
|
||||||
coroutines = "1.8.1"
|
coroutines = "1.8.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'io.github.wulkanowy:sdk:2.7.0'
|
implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines"
|
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.13.1'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
implementation "androidx.activity:activity-ktx:1.9.0"
|
implementation "androidx.activity:activity-ktx:1.8.2"
|
||||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.7.1"
|
implementation "androidx.fragment:fragment-ktx:1.6.2"
|
||||||
implementation "androidx.annotation:annotation:1.8.0"
|
implementation "androidx.annotation:annotation:1.7.1"
|
||||||
implementation "androidx.javascriptengine:javascriptengine:1.0.0-beta01"
|
|
||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.2.1"
|
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||||
implementation "androidx.viewpager2:viewpager2:1.1.0"
|
implementation "androidx.viewpager2:viewpager2:1.1.0-beta02"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||||
|
@ -221,7 +223,7 @@ dependencies {
|
||||||
implementation "androidx.work:work-runtime:$work_manager"
|
implementation "androidx.work:work-runtime:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.2"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room"
|
implementation "androidx.room:room-runtime:$room"
|
||||||
implementation "androidx.room:room-ktx:$room"
|
implementation "androidx.room:room-ktx:$room"
|
||||||
|
@ -235,7 +237,7 @@ dependencies {
|
||||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||||
|
@ -248,9 +250,9 @@ dependencies {
|
||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:33.0.0')
|
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics'
|
playImplementation 'com.google.firebase:firebase-analytics'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging'
|
playImplementation 'com.google.firebase:firebase-messaging'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
|
@ -276,7 +278,7 @@ dependencies {
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.12.2'
|
testImplementation 'org.robolectric:robolectric:4.11.1'
|
||||||
testImplementation "androidx.test:runner:1.5.2"
|
testImplementation "androidx.test:runner:1.5.2"
|
||||||
testImplementation "androidx.test.ext:junit:1.1.5"
|
testImplementation "androidx.test.ext:junit:1.1.5"
|
||||||
testImplementation "androidx.test:core:1.5.0"
|
testImplementation "androidx.test:core:1.5.0"
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#!/bin/bash -
|
#!/bin/bash -
|
||||||
|
|
||||||
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
|
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
|
||||||
content2=echo "$content" | dos2unix
|
if [[ "${#content}" -gt 500 ]]; then
|
||||||
if [[ "${#content2}" -gt 500 ]]; then
|
|
||||||
echo >&2 "Release notes content has reached the limit of 500 characters"
|
echo >&2 "Release notes content has reached the limit of 500 characters"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
92
app/src/debug/agconnect-services.json
Normal file
92
app/src/debug/agconnect-services.json
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"agcgw": {
|
||||||
|
"backurl": "connect-dre.hispace.hicloud.com",
|
||||||
|
"url": "connect-dre.dbankcloud.cn",
|
||||||
|
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
|
||||||
|
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
|
||||||
|
},
|
||||||
|
"agcgw_all": {
|
||||||
|
"CN": "connect-drcn.dbankcloud.cn",
|
||||||
|
"CN_back": "connect-drcn.hispace.hicloud.com",
|
||||||
|
"DE": "connect-dre.dbankcloud.cn",
|
||||||
|
"DE_back": "connect-dre.hispace.hicloud.com",
|
||||||
|
"RU": "connect-drru.hispace.dbankcloud.ru",
|
||||||
|
"RU_back": "connect-drru.hispace.dbankcloud.cn",
|
||||||
|
"SG": "connect-dra.dbankcloud.cn",
|
||||||
|
"SG_back": "connect-dra.hispace.hicloud.com"
|
||||||
|
},
|
||||||
|
"websocketgw_all": {
|
||||||
|
"CN": "connect-ws-drcn.hispace.dbankcloud.cn",
|
||||||
|
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
|
||||||
|
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
|
||||||
|
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
|
||||||
|
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
|
||||||
|
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
|
||||||
|
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
|
||||||
|
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"cp_id": "890048000024105546",
|
||||||
|
"product_id": "736430079244736562",
|
||||||
|
"client_id": "514530959291319360",
|
||||||
|
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
|
||||||
|
"project_id": "736430079244736562",
|
||||||
|
"app_id": "106552551",
|
||||||
|
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
|
||||||
|
"package_name": "io.github.wulkanowy.dev"
|
||||||
|
},
|
||||||
|
"oauth_client": {
|
||||||
|
"client_id": "106552551",
|
||||||
|
"client_type": 1
|
||||||
|
},
|
||||||
|
"app_info": {
|
||||||
|
"app_id": "106552551",
|
||||||
|
"package_name": "io.github.wulkanowy.dev"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"analytics": {
|
||||||
|
"collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||||
|
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
|
||||||
|
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
|
||||||
|
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||||
|
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
|
||||||
|
"resource_id": "p1",
|
||||||
|
"channel_id": ""
|
||||||
|
},
|
||||||
|
"search":{
|
||||||
|
"url":"https://search-dre.cloud.huawei.com"
|
||||||
|
},
|
||||||
|
"cloudstorage": {
|
||||||
|
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
|
||||||
|
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
|
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
|
"storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu",
|
||||||
|
"storage_url_de": "https://ops-dre.agcstorage.link",
|
||||||
|
"storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn",
|
||||||
|
"storage_url_sg": "https://ops-dra.agcstorage.link",
|
||||||
|
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
|
||||||
|
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
|
||||||
|
},
|
||||||
|
"ml": {
|
||||||
|
"mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"region": "DE",
|
||||||
|
"configuration_version": "3.0",
|
||||||
|
"appInfos": [
|
||||||
|
{
|
||||||
|
"package_name": "io.github.wulkanowy.dev",
|
||||||
|
"client": {
|
||||||
|
"app_id": "106552551"
|
||||||
|
},
|
||||||
|
"app_info": {
|
||||||
|
"package_name": "io.github.wulkanowy.dev",
|
||||||
|
"app_id": "106552551"
|
||||||
|
},
|
||||||
|
"oauth_client": {
|
||||||
|
"client_type": 1,
|
||||||
|
"client_id": "106552551"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -36,37 +36,6 @@
|
||||||
"status": 2
|
"status": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "io.github.wulkanowy"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
|
||||||
"status": 1,
|
|
||||||
"other_platform_oauth_client": []
|
|
||||||
},
|
|
||||||
"ads_service": {
|
|
||||||
"status": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration_version": "1"
|
"configuration_version": "1"
|
|
@ -3,8 +3,6 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:installLocation="internalOnly">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="androidx.javascriptengine" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
@ -44,16 +42,16 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:resizeableActivity="true"
|
|
||||||
android:supportsRtl="false"
|
android:supportsRtl="false"
|
||||||
android:theme="@style/WulkanowyTheme"
|
android:theme="@style/WulkanowyTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="DiscouragedApi,LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
@ -157,9 +155,33 @@
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||||
|
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||||
|
<provider
|
||||||
|
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||||
|
android:authorities="${applicationId}.firebaseinitprovider"
|
||||||
|
android:enabled="${firebase_enabled}"
|
||||||
|
android:exported="false"
|
||||||
|
tools:ignore="MissingClass" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="install_channel"
|
android:name="install_channel"
|
||||||
android:value="${install_channel}" />
|
android:value="${install_channel}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_analytics_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_crashlytics_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_messaging_auto_init_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
android:resource="@drawable/ic_stat_all" />
|
android:resource="@drawable/ic_stat_all" />
|
||||||
|
|
|
@ -13,18 +13,22 @@ import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
import io.github.wulkanowy.data.api.AdminMessageService
|
||||||
import io.github.wulkanowy.data.api.services.WulkanowyService
|
import io.github.wulkanowy.data.api.SchoolsService
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||||
|
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.create
|
import retrofit2.create
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -32,6 +36,20 @@ import javax.inject.Singleton
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal class DataModule {
|
internal class DataModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
|
||||||
|
Sdk().apply {
|
||||||
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
|
buildTag = android.os.Build.MODEL
|
||||||
|
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||||
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||||
|
|
||||||
|
// for debug only
|
||||||
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideChuckerCollector(
|
fun provideChuckerCollector(
|
||||||
|
@ -71,7 +89,7 @@ internal class DataModule {
|
||||||
okHttpClient: OkHttpClient,
|
okHttpClient: OkHttpClient,
|
||||||
json: Json,
|
json: Json,
|
||||||
appInfo: AppInfo
|
appInfo: AppInfo
|
||||||
): WulkanowyService = Retrofit.Builder()
|
): AdminMessageService = Retrofit.Builder()
|
||||||
.baseUrl(appInfo.messagesBaseUrl)
|
.baseUrl(appInfo.messagesBaseUrl)
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
package io.github.wulkanowy.data
|
package io.github.wulkanowy.data
|
||||||
|
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.filterNot
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -21,39 +14,16 @@ import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
sealed interface Resource<out T> {
|
sealed class Resource<T> {
|
||||||
/**
|
|
||||||
* The initial value of a resource flow. Indicates no data that is currently available to be shown,
|
open class Loading<T> : Resource<T>()
|
||||||
* however with the expectation that the state will transition to another one soon.
|
|
||||||
*/
|
|
||||||
open class Loading<T> : Resource<T>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A semi-loading state with some data available to be displayed (usually cached data loaded from
|
|
||||||
* the database). Still not the target state and it's expected to transition into another one soon.
|
|
||||||
*/
|
|
||||||
data class Intermediate<T>(val data: T) : Loading<T>()
|
data class Intermediate<T>(val data: T) : Loading<T>()
|
||||||
|
|
||||||
/**
|
data class Success<T>(val data: T) : Resource<T>()
|
||||||
* The happy-path target state. Data can either be:
|
|
||||||
* - loaded from the database - while it may seem like this case is already handled by the
|
|
||||||
* Intermediate state, the difference here is semantic. Cached data is returned as Intermediate
|
|
||||||
* when there's a API request in progress (or soon expected to be), however when there is no
|
|
||||||
* intention of immediately querying the API, the cached data is returned as a Success.
|
|
||||||
* - fetched from the API.
|
|
||||||
*/
|
|
||||||
data class Success<T>(val data: T) : Resource<T>
|
|
||||||
|
|
||||||
/**
|
data class Error<T>(val error: Throwable) : Resource<T>()
|
||||||
* Something bad happened and we were unable to get the requested data. This can be caused by
|
|
||||||
* a database error, a network error, or really just any other error. Upon receiving this state
|
|
||||||
* the UI can either: display a full screen error, or, when it has received any data previously,
|
|
||||||
* display a snack bar informing of the problem.
|
|
||||||
*/
|
|
||||||
data class Error<T>(val error: Throwable) : Resource<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val <T> Resource<T>.dataOrNull: T?
|
val <T> Resource<T>.dataOrNull: T?
|
||||||
|
@ -94,22 +64,6 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
|
||||||
is Resource.Error -> Resource.Error(this.error)
|
is Resource.Error -> Resource.Error(this.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects another flow into this flow's resource data.
|
|
||||||
*/
|
|
||||||
inline fun <T1, T2, R> Flow<Resource<T1>>.combineWithResourceData(
|
|
||||||
flow: Flow<T2>,
|
|
||||||
crossinline block: suspend (T1, T2) -> R
|
|
||||||
): Flow<Resource<R>> =
|
|
||||||
combine(flow) { resource, inject ->
|
|
||||||
when (resource) {
|
|
||||||
is Resource.Success -> Resource.Success(block(resource.data, inject))
|
|
||||||
is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject))
|
|
||||||
is Resource.Loading -> Resource.Loading()
|
|
||||||
is Resource.Error -> Resource.Error(resource.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
|
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
|
||||||
val description = when (it) {
|
val description = when (it) {
|
||||||
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
|
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
|
||||||
|
@ -120,29 +74,8 @@ fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = fa
|
||||||
Timber.i("$name: $description")
|
Timber.i("$name: $description")
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T, U> Flow<Resource<T>>.mapResourceData(crossinline block: suspend (T) -> U) = map {
|
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
|
||||||
when (it) {
|
it.mapData(block)
|
||||||
is Resource.Success -> Resource.Success(block(it.data))
|
|
||||||
is Resource.Intermediate -> Resource.Intermediate(block(it.data))
|
|
||||||
is Resource.Loading -> Resource.Loading()
|
|
||||||
is Resource.Error -> Resource.Error(it.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun <T, U> Flow<Resource<T>>.flatMapResourceData(
|
|
||||||
inheritIntermediate: Boolean = true, block: suspend (T) -> Flow<Resource<U>>
|
|
||||||
) = flatMapLatest {
|
|
||||||
when (it) {
|
|
||||||
is Resource.Success -> block(it.data)
|
|
||||||
is Resource.Intermediate -> block(it.data).map { newRes ->
|
|
||||||
if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data)
|
|
||||||
else newRes
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Loading -> flowOf(Resource.Loading())
|
|
||||||
is Resource.Error -> flowOf(Resource.Error(it.error))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
|
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
|
||||||
|
@ -172,13 +105,13 @@ fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Flow<Resource<T>>.onResourceError(block: suspend (Throwable) -> Unit) = onEach {
|
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
|
||||||
if (it is Resource.Error) {
|
if (it is Resource.Error) {
|
||||||
block(it.error)
|
block(it.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: suspend () -> Unit) = onEach {
|
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
|
||||||
if (it !is Resource.Loading) {
|
if (it !is Resource.Loading) {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
@ -188,100 +121,70 @@ suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loa
|
||||||
|
|
||||||
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
|
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
|
||||||
|
|
||||||
// Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired,
|
inline fun <ResultType, RequestType> networkBoundResource(
|
||||||
// use `debounceIntermediates` to alleviate this behavior.
|
|
||||||
inline fun <reified T> combineResourceFlows(flows: Iterable<Flow<Resource<T>>>): Flow<Resource<List<T>>> =
|
|
||||||
combine(flows) { items ->
|
|
||||||
var isIntermediate = false
|
|
||||||
val data = mutableListOf<T>()
|
|
||||||
for (item in items) {
|
|
||||||
when (item) {
|
|
||||||
is Resource.Success -> data.add(item.data)
|
|
||||||
is Resource.Intermediate -> {
|
|
||||||
isIntermediate = true
|
|
||||||
data.add(item.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Loading -> return@combine Resource.Loading()
|
|
||||||
is Resource.Error -> continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
// All items have to be errors for this to happen, so just return the first one.
|
|
||||||
// mapData is functionally useless and exists only to satisfy the type checker
|
|
||||||
items.first().mapData { listOf(it) }
|
|
||||||
} else if (isIntermediate) {
|
|
||||||
Resource.Intermediate(data)
|
|
||||||
} else {
|
|
||||||
Resource.Success(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
|
||||||
fun <T> Flow<Resource<T>>.debounceIntermediates(timeout: Duration = 5.seconds) = flow {
|
|
||||||
var wasIntermediate = false
|
|
||||||
|
|
||||||
emitAll(this@debounceIntermediates.debounce {
|
|
||||||
if (it is Resource.Intermediate) {
|
|
||||||
if (!wasIntermediate) {
|
|
||||||
wasIntermediate = true
|
|
||||||
Duration.ZERO
|
|
||||||
} else {
|
|
||||||
timeout
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wasIntermediate = false
|
|
||||||
Duration.ZERO
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inline fun <OutputType, ApiType> networkBoundResource(
|
|
||||||
mutex: Mutex = Mutex(),
|
mutex: Mutex = Mutex(),
|
||||||
crossinline isResultEmpty: (OutputType) -> Boolean,
|
showSavedOnLoading: Boolean = true,
|
||||||
crossinline query: () -> Flow<OutputType>,
|
crossinline isResultEmpty: (ResultType) -> Boolean,
|
||||||
crossinline fetch: suspend () -> ApiType,
|
crossinline query: () -> Flow<ResultType>,
|
||||||
crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit,
|
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||||
crossinline shouldFetch: (OutputType) -> Boolean = { true },
|
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||||
crossinline filterResult: (OutputType) -> OutputType = { it }
|
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||||
) = networkBoundResource(
|
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||||
mutex = mutex,
|
crossinline filterResult: (ResultType) -> ResultType = { it }
|
||||||
isResultEmpty = isResultEmpty,
|
|
||||||
query = query,
|
|
||||||
fetch = fetch,
|
|
||||||
saveFetchResult = saveFetchResult,
|
|
||||||
shouldFetch = shouldFetch,
|
|
||||||
mapResult = filterResult
|
|
||||||
)
|
|
||||||
|
|
||||||
@JvmName("networkBoundResourceWithMap")
|
|
||||||
inline fun <DatabaseType, ApiType, OutputType> networkBoundResource(
|
|
||||||
mutex: Mutex = Mutex(),
|
|
||||||
crossinline isResultEmpty: (OutputType) -> Boolean,
|
|
||||||
crossinline query: () -> Flow<DatabaseType>,
|
|
||||||
crossinline fetch: suspend () -> ApiType,
|
|
||||||
crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit,
|
|
||||||
crossinline shouldFetch: (DatabaseType) -> Boolean = { true },
|
|
||||||
crossinline mapResult: (DatabaseType) -> OutputType,
|
|
||||||
) = flow {
|
) = flow {
|
||||||
emit(Resource.Loading())
|
emit(Resource.Loading())
|
||||||
|
|
||||||
val data = query().first()
|
val data = query().first()
|
||||||
val updatedShouldFetch = if (isEndDateReached) false else shouldFetch(data)
|
emitAll(if (shouldFetch(data)) {
|
||||||
if (updatedShouldFetch) {
|
val filteredResult = filterResult(data)
|
||||||
emit(Resource.Intermediate(data))
|
|
||||||
|
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
|
||||||
|
emit(Resource.Intermediate(filteredResult))
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val newData = fetch()
|
val newData = fetch(data)
|
||||||
mutex.withLock { saveFetchResult(query().first(), newData) }
|
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||||
|
query().map { Resource.Success(filterResult(it)) }
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
emit(Resource.Error(throwable))
|
onFetchFailed(throwable)
|
||||||
return@flow
|
flowOf(Resource.Error(throwable))
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
query().map { Resource.Success(filterResult(it)) }
|
||||||
emitAll(query().map { Resource.Success(it) })
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("networkBoundResourceWithMap")
|
||||||
|
inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||||
|
mutex: Mutex = Mutex(),
|
||||||
|
showSavedOnLoading: Boolean = true,
|
||||||
|
crossinline isResultEmpty: (T) -> Boolean,
|
||||||
|
crossinline query: () -> Flow<ResultType>,
|
||||||
|
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||||
|
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||||
|
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||||
|
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||||
|
crossinline mapResult: (ResultType) -> T,
|
||||||
|
) = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
|
|
||||||
|
val data = query().first()
|
||||||
|
emitAll(if (shouldFetch(data)) {
|
||||||
|
val mappedResult = mapResult(data)
|
||||||
|
|
||||||
|
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
|
||||||
|
emit(Resource.Intermediate(mappedResult))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val newData = fetch(data)
|
||||||
|
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||||
|
query().map { Resource.Success(mapResult(it)) }
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
onFetchFailed(throwable)
|
||||||
|
flowOf(Resource.Error(throwable))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query().map { Resource.Success(mapResult(it)) }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.mapResourceData { mapResult(it) }
|
|
||||||
.filterNot { it is Resource.Intermediate && isResultEmpty(it.data) }
|
|
||||||
|
|
|
@ -1,174 +0,0 @@
|
||||||
package io.github.wulkanowy.data
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.javascriptengine.JavaScriptSandbox
|
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
|
||||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
|
||||||
import io.github.wulkanowy.sdk.scrapper.EvaluateHandler
|
|
||||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
|
||||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
|
||||||
import kotlinx.coroutines.guava.await
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class WulkanowySdkFactory @Inject constructor(
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val chuckerInterceptor: ChuckerInterceptor,
|
|
||||||
private val remoteConfig: RemoteConfigHelper,
|
|
||||||
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
|
||||||
private val studentDb: StudentDao,
|
|
||||||
private val wulkanowyRepository: WulkanowyRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val eduOneMutex = Mutex()
|
|
||||||
private val migrationFailedStudentIds = mutableSetOf<Long>()
|
|
||||||
private val sandbox: ListenableFuture<JavaScriptSandbox>? =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && JavaScriptSandbox.isSupported())
|
|
||||||
runCatching { JavaScriptSandbox.createConnectedInstanceAsync(context) }
|
|
||||||
.onFailure { Timber.e(it) }
|
|
||||||
.getOrNull()
|
|
||||||
else null
|
|
||||||
|
|
||||||
private val sdk = Sdk().apply {
|
|
||||||
androidVersion = Build.VERSION.RELEASE
|
|
||||||
buildTag = Build.MODEL
|
|
||||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
|
||||||
setAdditionalCookieManager(webkitCookieManagerProxy)
|
|
||||||
|
|
||||||
// for debug only
|
|
||||||
addInterceptor(chuckerInterceptor, network = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createBase() = sdk
|
|
||||||
|
|
||||||
suspend fun create(): Sdk {
|
|
||||||
val mapping = wulkanowyRepository.getMapping()
|
|
||||||
|
|
||||||
return createBase().apply {
|
|
||||||
if (mapping != null) {
|
|
||||||
endpointsMapping = mapping.endpoints
|
|
||||||
vTokenMapping = mapping.vTokens
|
|
||||||
vHeaders = mapping.vHeaders
|
|
||||||
responseMapping = mapping.responseMap
|
|
||||||
vParamsEvaluation = createIsolate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun createIsolate(): suspend () -> EvaluateHandler {
|
|
||||||
return {
|
|
||||||
val isolate = sandbox?.await()?.createIsolate()
|
|
||||||
object : EvaluateHandler {
|
|
||||||
override suspend fun evaluate(code: String): String? {
|
|
||||||
return isolate?.evaluateJavaScriptAsync(code)?.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
isolate?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun create(student: Student, semester: Semester? = null): Sdk {
|
|
||||||
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
|
||||||
return buildSdk(student, semester, overrideIsEduOne)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun buildSdk(
|
|
||||||
student: Student,
|
|
||||||
semester: Semester?,
|
|
||||||
isStudentEduOne: Boolean
|
|
||||||
): Sdk {
|
|
||||||
return create().apply {
|
|
||||||
email = student.email
|
|
||||||
password = student.password
|
|
||||||
symbol = student.symbol
|
|
||||||
schoolSymbol = student.schoolSymbol
|
|
||||||
studentId = student.studentId
|
|
||||||
classId = student.classId
|
|
||||||
emptyCookieJarInterceptor = true
|
|
||||||
isEduOne = isStudentEduOne
|
|
||||||
|
|
||||||
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
|
||||||
mobileBaseUrl = student.mobileBaseUrl
|
|
||||||
} else {
|
|
||||||
scrapperBaseUrl = student.scrapperBaseUrl
|
|
||||||
domainSuffix = student.scrapperDomainSuffix
|
|
||||||
loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
|
|
||||||
}
|
|
||||||
|
|
||||||
mode = Sdk.Mode.valueOf(student.loginMode)
|
|
||||||
mobileBaseUrl = student.mobileBaseUrl
|
|
||||||
keyId = student.certificateKey
|
|
||||||
privatePem = student.privateKey
|
|
||||||
|
|
||||||
if (semester != null) {
|
|
||||||
diaryId = semester.diaryId
|
|
||||||
kindergartenDiaryId = semester.kindergartenDiaryId
|
|
||||||
schoolYear = semester.schoolYear
|
|
||||||
unitId = semester.unitId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
|
|
||||||
if (student.isEduOne != null) return student.isEduOne
|
|
||||||
|
|
||||||
if (student.id in migrationFailedStudentIds) {
|
|
||||||
Timber.i("Migration eduOne: skipping because of previous failure")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
eduOneMutex.withLock {
|
|
||||||
if (student.id in migrationFailedStudentIds) {
|
|
||||||
Timber.i("Migration eduOne: skipping because of previous failure")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val studentFromDatabase = studentDb.loadById(student.id)
|
|
||||||
if (studentFromDatabase?.isEduOne != null) {
|
|
||||||
Timber.i("Migration eduOne: already done")
|
|
||||||
return studentFromDatabase.isEduOne
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("Migration eduOne: flag missing. Running migration...")
|
|
||||||
val initializedSdk = buildSdk(
|
|
||||||
student = student,
|
|
||||||
semester = null,
|
|
||||||
isStudentEduOne = false, // doesn't matter
|
|
||||||
)
|
|
||||||
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
|
||||||
.onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
|
|
||||||
.getOrNull()
|
|
||||||
|
|
||||||
if (newCurrentStudent == null) {
|
|
||||||
Timber.i("Migration eduOne: failed, so skipping")
|
|
||||||
migrationFailedStudentIds.add(student.id)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
|
|
||||||
|
|
||||||
val studentIsEduOne = StudentIsEduOne(
|
|
||||||
id = student.id,
|
|
||||||
isEduOne = newCurrentStudent.isEduOne
|
|
||||||
)
|
|
||||||
studentDb.update(studentIsEduOne)
|
|
||||||
return newCurrentStudent.isEduOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,12 @@
|
||||||
package io.github.wulkanowy.data.api.services
|
package io.github.wulkanowy.data.api
|
||||||
|
|
||||||
import io.github.wulkanowy.data.api.models.Mapping
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
interface WulkanowyService {
|
interface AdminMessageService {
|
||||||
|
|
||||||
@GET("/v1.json")
|
@GET("/v1.json")
|
||||||
suspend fun getAdminMessages(): List<AdminMessage>
|
suspend fun getAdminMessages(): List<AdminMessage>
|
||||||
|
}
|
||||||
@GET("/mapping4.json")
|
|
||||||
suspend fun getMapping(): Mapping
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.wulkanowy.data.api.services
|
package io.github.wulkanowy.data.api
|
||||||
|
|
||||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
import io.github.wulkanowy.data.pojos.LoginEvent
|
|
@ -1,23 +0,0 @@
|
||||||
package io.github.wulkanowy.data.api.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Mapping(
|
|
||||||
|
|
||||||
@SerialName("endpoints")
|
|
||||||
val endpoints: Map<String, Map<String, Map<String, String>>>,
|
|
||||||
|
|
||||||
@SerialName("vTokens")
|
|
||||||
val vTokens: Map<String, Map<String, Map<String, String>>>,
|
|
||||||
|
|
||||||
@SerialName("vTokenScheme")
|
|
||||||
val vTokenScheme: Map<String, Map<String, String>> = emptyMap(),
|
|
||||||
|
|
||||||
@SerialName("vHeaders")
|
|
||||||
val vHeaders: Map<String, Map<String, Map<String, String>>> = emptyMap(),
|
|
||||||
|
|
||||||
@SerialName("responseMap")
|
|
||||||
val responseMap: Map<String, Map<String, Map<String, Map<String, String>>>> = emptyMap(),
|
|
||||||
)
|
|
|
@ -120,7 +120,6 @@ import io.github.wulkanowy.data.db.migrations.Migration55
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration57
|
import io.github.wulkanowy.data.db.migrations.Migration57
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration58
|
import io.github.wulkanowy.data.db.migrations.Migration58
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration63
|
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||||
|
@ -174,10 +173,6 @@ import javax.inject.Singleton
|
||||||
AutoMigration(from = 57, to = 58, spec = Migration58::class),
|
AutoMigration(from = 57, to = 58, spec = Migration58::class),
|
||||||
AutoMigration(from = 58, to = 59),
|
AutoMigration(from = 58, to = 59),
|
||||||
AutoMigration(from = 59, to = 60),
|
AutoMigration(from = 59, to = 60),
|
||||||
AutoMigration(from = 60, to = 61),
|
|
||||||
AutoMigration(from = 61, to = 62),
|
|
||||||
AutoMigration(from = 62, to = 63, spec = Migration63::class),
|
|
||||||
AutoMigration(from = 63, to = 64),
|
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
|
@ -186,7 +181,7 @@ import javax.inject.Singleton
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 64
|
const val VERSION_SCHEMA = 60
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
|
@ -313,6 +308,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract val adminMessagesDao: AdminMessageDao
|
abstract val adminMessagesDao: AdminMessageDao
|
||||||
|
|
||||||
abstract val mutedMessageSendersDao: MutedMessageSendersDao
|
abstract val mutedMessageSendersDao: MutedMessageSendersDao
|
||||||
|
|
||||||
abstract val gradeDescriptiveDao: GradeDescriptiveDao
|
abstract val gradeDescriptiveDao: GradeDescriptiveDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,24 @@ package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Dao
|
@Dao
|
||||||
interface AdminMessageDao : BaseDao<AdminMessage> {
|
abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
||||||
|
|
||||||
@Query("SELECT * FROM AdminMessages")
|
@Query("SELECT * FROM AdminMessages")
|
||||||
fun loadAll(): Flow<List<AdminMessage>>
|
abstract fun loadAll(): Flow<List<AdminMessage>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun removeOldAndSaveNew(
|
||||||
|
oldMessages: List<AdminMessage>,
|
||||||
|
newMessages: List<AdminMessage>
|
||||||
|
) {
|
||||||
|
deleteAll(oldMessages)
|
||||||
|
insertAll(newMessages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.data.db.dao
|
||||||
import androidx.room.Delete
|
import androidx.room.Delete
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
|
|
||||||
interface BaseDao<T> {
|
interface BaseDao<T> {
|
||||||
|
@ -16,10 +15,4 @@ interface BaseDao<T> {
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun deleteAll(items: List<T>)
|
suspend fun deleteAll(items: List<T>)
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun removeOldAndSaveNew(oldItems: List<T>, newItems: List<T>) {
|
|
||||||
deleteAll(oldItems)
|
|
||||||
insertAll(newItems)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
|
||||||
@Dao
|
@Dao
|
||||||
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
||||||
|
|
||||||
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC")
|
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||||
fun loadAll(studentId: Int): Flow<List<MobileDevice>>
|
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
||||||
|
|
||||||
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC")
|
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||||
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
|
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ interface SemesterDao : BaseDao<Semester> {
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insertSemesters(items: List<Semester>): List<Long>
|
suspend fun insertSemesters(items: List<Semester>): List<Long>
|
||||||
|
|
||||||
@Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)")
|
@Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
|
||||||
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
|
|
||||||
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
|
||||||
import io.github.wulkanowy.data.db.entities.StudentName
|
import io.github.wulkanowy.data.db.entities.StudentName
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -25,12 +23,6 @@ abstract class StudentDao {
|
||||||
@Delete
|
@Delete
|
||||||
abstract suspend fun delete(student: Student)
|
abstract suspend fun delete(student: Student)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
|
||||||
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
|
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
|
||||||
abstract suspend fun update(studentIsEduOne: StudentIsEduOne)
|
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
|
@ -47,11 +39,11 @@ abstract class StudentDao {
|
||||||
abstract suspend fun loadAll(): List<Student>
|
abstract suspend fun loadAll(): List<Student>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)")
|
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id")
|
||||||
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
|
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id")
|
||||||
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||||
|
|
|
@ -4,8 +4,6 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import io.github.wulkanowy.data.enums.MessageType
|
import io.github.wulkanowy.data.enums.MessageType
|
||||||
import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -36,8 +34,6 @@ data class AdminMessage(
|
||||||
|
|
||||||
val priority: String,
|
val priority: String,
|
||||||
|
|
||||||
@SerialName("messageTypes")
|
|
||||||
@Serializable(with = SafeMessageTypeEnumListSerializer::class)
|
|
||||||
@ColumnInfo(name = "types", defaultValue = "[]")
|
@ColumnInfo(name = "types", defaultValue = "[]")
|
||||||
val types: List<MessageType> = emptyList(),
|
val types: List<MessageType> = emptyList(),
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,7 @@ data class GradeSummary(
|
||||||
@ColumnInfo(name = "points_sum")
|
@ColumnInfo(name = "points_sum")
|
||||||
val pointsSum: String,
|
val pointsSum: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "points_sum_all_year")
|
val average: Double
|
||||||
val pointsSumAllYear: String?,
|
|
||||||
|
|
||||||
val average: Double,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "average_all_year")
|
|
||||||
val averageAllYear: Double? = null,
|
|
||||||
) {
|
) {
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
|
@ -9,8 +9,8 @@ import java.time.Instant
|
||||||
@Entity(tableName = "MobileDevices")
|
@Entity(tableName = "MobileDevices")
|
||||||
data class MobileDevice(
|
data class MobileDevice(
|
||||||
|
|
||||||
@ColumnInfo(name = "user_login_id") // todo: change column name
|
@ColumnInfo(name = "user_login_id")
|
||||||
val studentId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = "device_id")
|
@ColumnInfo(name = "device_id")
|
||||||
val deviceId: Int,
|
val deviceId: Int,
|
||||||
|
|
|
@ -9,16 +9,14 @@ import java.time.LocalDate
|
||||||
@Entity(tableName = "SchoolAnnouncements")
|
@Entity(tableName = "SchoolAnnouncements")
|
||||||
data class SchoolAnnouncement(
|
data class SchoolAnnouncement(
|
||||||
|
|
||||||
@ColumnInfo(name = "user_login_id") // todo: change column name
|
@ColumnInfo(name = "user_login_id")
|
||||||
val studentId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
|
|
||||||
val subject: String,
|
val subject: String,
|
||||||
|
|
||||||
val content: String,
|
val content: String
|
||||||
|
|
||||||
val author: String? = null,
|
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
|
|
@ -49,7 +49,6 @@ data class Student(
|
||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "student_id")
|
||||||
val studentId: Int,
|
val studentId: Int,
|
||||||
|
|
||||||
@Deprecated("not available in VULCAN anymore")
|
|
||||||
@ColumnInfo(name = "user_login_id")
|
@ColumnInfo(name = "user_login_id")
|
||||||
val userLoginId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
|
@ -79,13 +78,6 @@ data class Student(
|
||||||
|
|
||||||
@ColumnInfo(name = "registration_date")
|
@ColumnInfo(name = "registration_date")
|
||||||
val registrationDate: Instant,
|
val registrationDate: Instant,
|
||||||
|
|
||||||
@ColumnInfo(name = "is_authorized", defaultValue = "0")
|
|
||||||
val isAuthorized: Boolean,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
|
||||||
val isEduOne: Boolean?,
|
|
||||||
|
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ -96,22 +88,3 @@ data class Student(
|
||||||
@ColumnInfo(name = "avatar_color")
|
@ColumnInfo(name = "avatar_color")
|
||||||
var avatarColor = 0L
|
var avatarColor = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class StudentIsAuthorized(
|
|
||||||
|
|
||||||
@PrimaryKey
|
|
||||||
var id: Long,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "is_authorized", defaultValue = "NULL")
|
|
||||||
val isAuthorized: Boolean?,
|
|
||||||
) : Serializable
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class StudentIsEduOne(
|
|
||||||
@PrimaryKey
|
|
||||||
var id: Long,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
|
||||||
val isEduOne: Boolean?,
|
|
||||||
) : Serializable
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.migrations
|
|
||||||
|
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
|
|
||||||
class Migration63 : AutoMigrationSpec {
|
|
||||||
|
|
||||||
override fun onPostMigrate(db: SupportSQLiteDatabase) {
|
|
||||||
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package io.github.wulkanowy.data.enums
|
|
||||||
|
|
||||||
enum class AttendanceCalculatorSortingMode(private val value: String) {
|
|
||||||
ALPHABETIC("alphabetic"),
|
|
||||||
ATTENDANCE("attendance_percentage"),
|
|
||||||
LESSON_BALANCE("lesson_balance");
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getByValue(value: String) =
|
|
||||||
AttendanceCalculatorSortingMode.values()
|
|
||||||
.find { it.value == value } ?: ALPHABETIC
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,8 +4,6 @@ enum class MessageType {
|
||||||
GENERAL_MESSAGE,
|
GENERAL_MESSAGE,
|
||||||
DASHBOARD_MESSAGE,
|
DASHBOARD_MESSAGE,
|
||||||
LOGIN_MESSAGE,
|
LOGIN_MESSAGE,
|
||||||
LOGIN_STUDENT_SELECT_MESSAGE,
|
|
||||||
LOGIN_SYMBOL_MESSAGE,
|
|
||||||
PASS_RESET_MESSAGE,
|
PASS_RESET_MESSAGE,
|
||||||
ERROR_OVERRIDE,
|
ERROR_OVERRIDE,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package io.github.wulkanowy.data.enums
|
|
||||||
|
|
||||||
enum class ShowAdditionalLessonsMode(val value: String) {
|
|
||||||
NONE("none"),
|
|
||||||
INLINE("inline"),
|
|
||||||
BELOW("below");
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,26 +3,12 @@ package io.github.wulkanowy.data.mappers
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
|
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
|
||||||
import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
|
|
||||||
|
|
||||||
@JvmName("mapDirectorInformationToEntities")
|
|
||||||
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
||||||
SchoolAnnouncement(
|
SchoolAnnouncement(
|
||||||
studentId = student.studentId,
|
userLoginId = student.userLoginId,
|
||||||
date = it.date,
|
date = it.date,
|
||||||
subject = it.subject,
|
subject = it.subject,
|
||||||
content = it.content,
|
content = it.content,
|
||||||
author = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("mapLastAnnouncementsToEntities")
|
|
||||||
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
|
|
||||||
SchoolAnnouncement(
|
|
||||||
studentId = student.studentId,
|
|
||||||
date = it.date,
|
|
||||||
subject = it.subject,
|
|
||||||
content = it.content,
|
|
||||||
author = it.author,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,9 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
|
||||||
predictedGrade = it.predicted,
|
predictedGrade = it.predicted,
|
||||||
finalGrade = it.final,
|
finalGrade = it.final,
|
||||||
pointsSum = it.pointsSum,
|
pointsSum = it.pointsSum,
|
||||||
pointsSumAllYear = it.pointsSumAllYear,
|
|
||||||
proposedPoints = it.proposedPoints,
|
proposedPoints = it.proposedPoints,
|
||||||
finalPoints = it.finalPoints,
|
finalPoints = it.finalPoints,
|
||||||
average = it.average,
|
average = it.average
|
||||||
averageAllYear = it.averageAllYear,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
|
||||||
|
|
||||||
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
||||||
MobileDevice(
|
MobileDevice(
|
||||||
studentId = student.studentId,
|
userLoginId = student.userLoginId,
|
||||||
date = it.createDate.toInstant(),
|
date = it.createDate.toInstant(),
|
||||||
deviceId = it.id,
|
deviceId = it.id,
|
||||||
name = it.name
|
name = it.name
|
||||||
|
|
|
@ -34,19 +34,17 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
|
||||||
error = it.error,
|
error = it.error,
|
||||||
students = it.subjects
|
students = it.subjects
|
||||||
.filterIsInstance<SdkRegisterStudent>()
|
.filterIsInstance<SdkRegisterStudent>()
|
||||||
.map { registerStudent ->
|
.map { registerSubject ->
|
||||||
RegisterStudent(
|
RegisterStudent(
|
||||||
studentId = registerStudent.studentId,
|
studentId = registerSubject.studentId,
|
||||||
studentName = registerStudent.studentName,
|
studentName = registerSubject.studentName,
|
||||||
studentSecondName = registerStudent.studentSecondName,
|
studentSecondName = registerSubject.studentSecondName,
|
||||||
studentSurname = registerStudent.studentSurname,
|
studentSurname = registerSubject.studentSurname,
|
||||||
className = registerStudent.className,
|
className = registerSubject.className,
|
||||||
classId = registerStudent.classId,
|
classId = registerSubject.classId,
|
||||||
isParent = registerStudent.isParent,
|
isParent = registerSubject.isParent,
|
||||||
isAuthorized = registerStudent.isAuthorized,
|
semesters = registerSubject.semesters
|
||||||
isEduOne = registerStudent.isEduOne,
|
.mapToEntities(registerSubject.studentId),
|
||||||
semesters = registerStudent.semesters
|
|
||||||
.mapToEntities(registerStudent.studentId),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -86,8 +84,6 @@ fun RegisterStudent.mapToStudentWithSemesters(
|
||||||
password = user.password.orEmpty(),
|
password = user.password.orEmpty(),
|
||||||
isCurrent = false,
|
isCurrent = false,
|
||||||
registrationDate = Instant.now(),
|
registrationDate = Instant.now(),
|
||||||
isAuthorized = this.isAuthorized,
|
|
||||||
isEduOne = this.isEduOne,
|
|
||||||
).apply {
|
).apply {
|
||||||
avatarColor = colors.random()
|
avatarColor = colors.random()
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package io.github.wulkanowy.data.pojos
|
|
||||||
|
|
||||||
data class AttendanceData(
|
|
||||||
val subjectName: String,
|
|
||||||
val lessonBalance: Int,
|
|
||||||
val presences: Int,
|
|
||||||
val absences: Int,
|
|
||||||
) {
|
|
||||||
val total: Int
|
|
||||||
get() = presences + absences
|
|
||||||
|
|
||||||
val presencePercentage: Double
|
|
||||||
get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100
|
|
||||||
}
|
|
|
@ -45,6 +45,4 @@ data class RegisterStudent(
|
||||||
val classId: Int,
|
val classId: Int,
|
||||||
val isParent: Boolean,
|
val isParent: Boolean,
|
||||||
val semesters: List<Semester>,
|
val semesters: List<Semester>,
|
||||||
val isAuthorized: Boolean,
|
|
||||||
val isEduOne: Boolean
|
|
||||||
) : java.io.Serializable
|
) : java.io.Serializable
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.Resource
|
||||||
|
import io.github.wulkanowy.data.api.AdminMessageService
|
||||||
|
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class AdminMessageRepository @Inject constructor(
|
||||||
|
private val adminMessageService: AdminMessageService,
|
||||||
|
private val adminMessageDao: AdminMessageDao,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
|
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
|
||||||
|
networkBoundResource(
|
||||||
|
mutex = saveFetchResultMutex,
|
||||||
|
isResultEmpty = { false },
|
||||||
|
query = { adminMessageDao.loadAll() },
|
||||||
|
fetch = { adminMessageService.getAdminMessages() },
|
||||||
|
shouldFetch = { true },
|
||||||
|
saveFetchResult = { oldItems, newItems ->
|
||||||
|
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||||
|
},
|
||||||
|
showSavedOnLoading = false,
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
@ -8,11 +7,14 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Absent
|
import io.github.wulkanowy.sdk.pojo.Absent
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -26,7 +28,7 @@ import javax.inject.Singleton
|
||||||
class AttendanceRepository @Inject constructor(
|
class AttendanceRepository @Inject constructor(
|
||||||
private val attendanceDb: AttendanceDao,
|
private val attendanceDb: AttendanceDao,
|
||||||
private val timetableDb: TimetableDao,
|
private val timetableDb: TimetableDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -57,18 +59,18 @@ class AttendanceRepository @Inject constructor(
|
||||||
val lessons = timetableDb.load(
|
val lessons = timetableDb.load(
|
||||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||||
)
|
)
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getAttendance(start.monday, end.sunday)
|
.getAttendance(start.monday, end.sunday)
|
||||||
.mapToEntities(semester, lessons)
|
.mapToEntities(semester, lessons)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
|
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||||
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
||||||
newAttendance.apply { if (notify) isNotified = false }
|
newAttendance.apply { if (notify) isNotified = false }
|
||||||
}
|
}
|
||||||
attendanceDb.removeOldAndSaveNew(
|
attendanceDb.insertAll(attendanceToAdd)
|
||||||
oldItems = old uniqueSubtract new,
|
|
||||||
newItems = attendanceToAdd,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
},
|
},
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
|
@ -86,6 +88,7 @@ class AttendanceRepository @Inject constructor(
|
||||||
return attendanceDb.updateAll(timetable)
|
return attendanceDb.updateAll(timetable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmName("excuseForAbsenceLessons")
|
||||||
suspend fun excuseForAbsence(
|
suspend fun excuseForAbsence(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
|
@ -98,7 +101,26 @@ class AttendanceRepository @Inject constructor(
|
||||||
timeId = attendance.timeId
|
timeId = attendance.timeId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
|
.excuseForAbsence(items, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("excuseForAbsenceDays")
|
||||||
|
suspend fun excuseForAbsence(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
days: List<LocalDate>,
|
||||||
|
reason: String? = null
|
||||||
|
) {
|
||||||
|
val items = days.map { day ->
|
||||||
|
Absent(
|
||||||
|
date = LocalDateTime.of(day, LocalTime.of(0, 0)),
|
||||||
|
timeId = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.excuseForAbsence(items, reason)
|
.excuseForAbsence(items, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import androidx.room.withTransaction
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -18,9 +18,8 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class AttendanceSummaryRepository @Inject constructor(
|
class AttendanceSummaryRepository @Inject constructor(
|
||||||
private val attendanceDb: AttendanceSummaryDao,
|
private val attendanceDb: AttendanceSummaryDao,
|
||||||
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
private val appDatabase: AppDatabase,
|
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
@ -41,15 +40,14 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getAttendanceSummary(subjectId)
|
.getAttendanceSummary(subjectId)
|
||||||
.mapToEntities(semester, subjectId)
|
.mapToEntities(semester, subjectId)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
appDatabase.withTransaction {
|
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
attendanceDb.insertAll(new uniqueSubtract old)
|
||||||
attendanceDb.insertAll(new uniqueSubtract old)
|
|
||||||
}
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.monday
|
|
||||||
import io.github.wulkanowy.utils.sunday
|
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -19,7 +15,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class CompletedLessonsRepository @Inject constructor(
|
class CompletedLessonsRepository @Inject constructor(
|
||||||
private val completedLessonsDb: CompletedLessonsDao,
|
private val completedLessonsDb: CompletedLessonsDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -51,15 +47,14 @@ class CompletedLessonsRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
completedLessonsDb.removeOldAndSaveNew(
|
completedLessonsDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
completedLessonsDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
},
|
},
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -19,7 +21,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class ConferenceRepository @Inject constructor(
|
class ConferenceRepository @Inject constructor(
|
||||||
private val conferenceDb: ConferenceDao,
|
private val conferenceDb: ConferenceDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -44,18 +46,19 @@ class ConferenceRepository @Inject constructor(
|
||||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getConferences()
|
.getConferences()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
.filter { it.date >= startDate }
|
.filter { it.date >= startDate }
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
conferenceDb.removeOldAndSaveNew(
|
val conferencesToSave = (new uniqueSubtract old).onEach {
|
||||||
oldItems = old uniqueSubtract new,
|
if (notify) it.isNotified = false
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
}
|
||||||
if (notify) it.isNotified = false
|
|
||||||
},
|
conferenceDb.deleteAll(old uniqueSubtract new)
|
||||||
)
|
conferenceDb.insertAll(conferencesToSave)
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.endExamsDay
|
import io.github.wulkanowy.utils.endExamsDay
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.startExamsDay
|
import io.github.wulkanowy.utils.startExamsDay
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -21,7 +23,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class ExamRepository @Inject constructor(
|
class ExamRepository @Inject constructor(
|
||||||
private val examDb: ExamDao,
|
private val examDb: ExamDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -54,17 +56,18 @@ class ExamRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getExams(start.startExamsDay, start.endExamsDay)
|
.getExams(start.startExamsDay, start.endExamsDay)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
examDb.removeOldAndSaveNew(
|
val examsToSave = (new uniqueSubtract old).onEach {
|
||||||
oldItems = old uniqueSubtract new,
|
if (notify) it.isNotified = false
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
}
|
||||||
if (notify) it.isNotified = false
|
|
||||||
},
|
examDb.deleteAll(old uniqueSubtract new)
|
||||||
)
|
examDb.insertAll(examsToSave)
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
},
|
},
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
|
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||||
|
@ -11,8 +10,11 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.toLocalDate
|
import io.github.wulkanowy.utils.toLocalDate
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -28,7 +30,7 @@ class GradeRepository @Inject constructor(
|
||||||
private val gradeDb: GradeDao,
|
private val gradeDb: GradeDao,
|
||||||
private val gradeSummaryDb: GradeSummaryDao,
|
private val gradeSummaryDb: GradeSummaryDao,
|
||||||
private val gradeDescriptiveDb: GradeDescriptiveDao,
|
private val gradeDescriptiveDb: GradeDescriptiveDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -61,7 +63,8 @@ class GradeRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester)
|
val (details, summary, descriptive) = sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getGrades(semester.semesterId)
|
.getGrades(semester.semesterId)
|
||||||
|
|
||||||
Triple(
|
Triple(
|
||||||
|
@ -84,12 +87,10 @@ class GradeRepository @Inject constructor(
|
||||||
new: List<GradeDescriptive>,
|
new: List<GradeDescriptive>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
gradeDescriptiveDb.removeOldAndSaveNew(
|
gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
if (notify) it.isNotified = false
|
||||||
if (notify) it.isNotified = false
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshGradeDetails(
|
private suspend fun refreshGradeDetails(
|
||||||
|
@ -100,16 +101,13 @@ class GradeRepository @Inject constructor(
|
||||||
) {
|
) {
|
||||||
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
|
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
|
||||||
?: student.registrationDate.toLocalDate()
|
?: student.registrationDate.toLocalDate()
|
||||||
|
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||||
gradeDb.removeOldAndSaveNew(
|
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||||
oldItems = oldGrades uniqueSubtract newDetails,
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
newItems = (newDetails uniqueSubtract oldGrades).onEach {
|
isRead = false
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (notify) isNotified = false
|
||||||
isRead = false
|
}
|
||||||
if (notify) isNotified = false
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshGradeSummaries(
|
private suspend fun refreshGradeSummaries(
|
||||||
|
@ -117,43 +115,31 @@ class GradeRepository @Inject constructor(
|
||||||
newSummary: List<GradeSummary>,
|
newSummary: List<GradeSummary>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
gradeSummaryDb.removeOldAndSaveNew(
|
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||||
oldItems = oldSummaries uniqueSubtract newSummary,
|
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||||
newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||||
getGradeSummaryWithUpdatedNotificationState(
|
summary.isPredictedGradeNotified = when {
|
||||||
summary = summary,
|
summary.predictedGrade.isEmpty() -> true
|
||||||
oldSummary = oldSummaries.find { it.subject == summary.subject },
|
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||||
notify = notify,
|
else -> true
|
||||||
)
|
}
|
||||||
},
|
summary.isFinalGradeNotified = when {
|
||||||
)
|
summary.finalGrade.isEmpty() -> true
|
||||||
}
|
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
private fun getGradeSummaryWithUpdatedNotificationState(
|
summary.predictedGradeLastChange = when {
|
||||||
summary: GradeSummary,
|
oldSummary == null -> Instant.now()
|
||||||
oldSummary: GradeSummary?,
|
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||||
notify: Boolean,
|
else -> oldSummary.predictedGradeLastChange
|
||||||
) {
|
}
|
||||||
summary.isPredictedGradeNotified = when {
|
summary.finalGradeLastChange = when {
|
||||||
summary.predictedGrade.isEmpty() -> true
|
oldSummary == null -> Instant.now()
|
||||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
||||||
else -> true
|
else -> oldSummary.finalGradeLastChange
|
||||||
}
|
}
|
||||||
summary.isFinalGradeNotified = when {
|
})
|
||||||
summary.finalGrade.isEmpty() -> true
|
|
||||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
summary.predictedGradeLastChange = when {
|
|
||||||
oldSummary == null -> Instant.now()
|
|
||||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
|
||||||
else -> oldSummary.predictedGradeLastChange
|
|
||||||
}
|
|
||||||
summary.finalGradeLastChange = when {
|
|
||||||
oldSummary == null -> Instant.now()
|
|
||||||
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
|
||||||
else -> oldSummary.finalGradeLastChange
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
|
||||||
|
@ -13,11 +12,14 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
|
||||||
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
|
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
private val gradePartialStatisticsDb: GradePartialStatisticsDao,
|
private val gradePartialStatisticsDb: GradePartialStatisticsDao,
|
||||||
private val gradePointsStatisticsDb: GradePointsStatisticsDao,
|
private val gradePointsStatisticsDb: GradePointsStatisticsDao,
|
||||||
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao,
|
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -54,15 +56,14 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getGradesPartialStatistics(semester.semesterId)
|
.getGradesPartialStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
gradePartialStatisticsDb.removeOldAndSaveNew(
|
gradePartialStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
gradePartialStatisticsDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
|
||||||
},
|
},
|
||||||
mapResult = { items ->
|
mapResult = { items ->
|
||||||
|
@ -79,7 +80,6 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
listOf(summaryItem) + items
|
listOf(summaryItem) + items
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> items.filter { it.subject == subjectName }
|
else -> items.filter { it.subject == subjectName }
|
||||||
}.mapPartialToStatisticItems()
|
}.mapPartialToStatisticItems()
|
||||||
}
|
}
|
||||||
|
@ -101,15 +101,14 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getGradesSemesterStatistics(semester.semesterId)
|
.getGradesSemesterStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
gradeSemesterStatisticsDb.removeOldAndSaveNew(
|
gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
|
||||||
},
|
},
|
||||||
mapResult = { items ->
|
mapResult = { items ->
|
||||||
|
@ -139,7 +138,6 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
listOf(summaryItem) + itemsWithAverage
|
listOf(summaryItem) + itemsWithAverage
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> itemsWithAverage.filter { it.subject == subjectName }
|
else -> itemsWithAverage.filter { it.subject == subjectName }
|
||||||
}.mapSemesterToStatisticItems()
|
}.mapSemesterToStatisticItems()
|
||||||
}
|
}
|
||||||
|
@ -159,15 +157,14 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getGradesPointsStatistics(semester.semesterId)
|
.getGradesPointsStatistics(semester.semesterId)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
gradePointsStatisticsDb.removeOldAndSaveNew(
|
gradePointsStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
gradePointsStatisticsDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
|
||||||
},
|
},
|
||||||
mapResult = { items ->
|
mapResult = { items ->
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||||
import io.github.wulkanowy.data.db.entities.Homework
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
@ -20,7 +22,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class HomeworkRepository @Inject constructor(
|
class HomeworkRepository @Inject constructor(
|
||||||
private val homeworkDb: HomeworkDao,
|
private val homeworkDb: HomeworkDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -53,19 +55,20 @@ class HomeworkRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getHomework(start.monday, end.sunday)
|
.getHomework(start.monday, end.sunday)
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
|
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||||
|
if (notify) it.isNotified = false
|
||||||
|
}
|
||||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||||
|
|
||||||
homeworkDb.removeOldAndSaveNew(
|
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||||
oldItems = filteredOld uniqueSubtract new,
|
homeworkDb.insertAll(homeWorkToSave)
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
|
||||||
if (notify) it.isNotified = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppWidgetUpdater
|
import io.github.wulkanowy.utils.init
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -19,8 +18,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class LuckyNumberRepository @Inject constructor(
|
class LuckyNumberRepository @Inject constructor(
|
||||||
private val luckyNumberDb: LuckyNumberDao,
|
private val luckyNumberDb: LuckyNumberDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk
|
||||||
private val appWidgetUpdater: AppWidgetUpdater,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
@ -29,28 +27,23 @@ class LuckyNumberRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
isFromAppWidget: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
isResultEmpty = { it == null },
|
isResultEmpty = { it == null },
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||||
.getLuckyNumber(student.schoolShortName)
|
|
||||||
?.mapToEntity(student)
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
|
saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
|
||||||
newLuckyNumber ?: return@networkBoundResource
|
newLuckyNumber ?: return@networkBoundResource
|
||||||
|
|
||||||
if (newLuckyNumber != oldLuckyNumber) {
|
if (newLuckyNumber != oldLuckyNumber) {
|
||||||
luckyNumberDb.removeOldAndSaveNew(
|
val updatedLuckNumberList =
|
||||||
oldItems = listOfNotNull(oldLuckyNumber),
|
listOf(newLuckyNumber.apply { if (notify) isNotified = false })
|
||||||
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
|
|
||||||
)
|
oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||||
if (!isFromAppWidget) {
|
luckyNumberDb.insertAll(updatedLuckNumberList)
|
||||||
appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||||
|
@ -30,9 +29,11 @@ import io.github.wulkanowy.data.pojos.MessageDraft
|
||||||
import io.github.wulkanowy.data.toFirstResult
|
import io.github.wulkanowy.data.toFirstResult
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Folder
|
import io.github.wulkanowy.sdk.pojo.Folder
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -47,7 +48,7 @@ class MessageRepository @Inject constructor(
|
||||||
private val messagesDb: MessagesDao,
|
private val messagesDb: MessagesDao,
|
||||||
private val mutedMessageSendersDao: MutedMessageSendersDao,
|
private val mutedMessageSendersDao: MutedMessageSendersDao,
|
||||||
private val messageAttachmentDao: MessageAttachmentDao,
|
private val messageAttachmentDao: MessageAttachmentDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
private val sharedPrefProvider: SharedPrefProvider,
|
private val sharedPrefProvider: SharedPrefProvider,
|
||||||
|
@ -81,26 +82,19 @@ class MessageRepository @Inject constructor(
|
||||||
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
|
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).getMessages(
|
||||||
.getMessages(
|
folder = Folder.valueOf(folder.name),
|
||||||
folder = Folder.valueOf(folder.name),
|
mailboxKey = mailbox?.globalKey,
|
||||||
mailboxKey = mailbox?.globalKey,
|
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
||||||
)
|
|
||||||
.mapToEntities(
|
|
||||||
student = student,
|
|
||||||
mailbox = mailbox,
|
|
||||||
allMailboxes = mailboxDao.loadAll(student.email)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { oldWithAuthors, new ->
|
saveFetchResult = { oldWithAuthors, new ->
|
||||||
val old = oldWithAuthors.map { it.message }
|
val old = oldWithAuthors.map { it.message }
|
||||||
messagesDb.removeOldAndSaveNew(
|
messagesDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
val muted = isMuted(it.correspondents)
|
||||||
val muted = isMuted(it.correspondents)
|
it.isNotified = !notify || muted
|
||||||
it.isNotified = !notify || muted
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(
|
refreshHelper.updateLastRefreshTimestamp(
|
||||||
getRefreshKey(messagesCacheKey, mailbox, folder)
|
getRefreshKey(messagesCacheKey, mailbox, folder)
|
||||||
)
|
)
|
||||||
|
@ -120,11 +114,10 @@ class MessageRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).getMessageDetails(
|
||||||
.getMessageDetails(
|
messageKey = it!!.message.messageGlobalKey,
|
||||||
messageKey = message.messageGlobalKey,
|
markAsRead = message.unread && markAsRead,
|
||||||
markAsRead = message.unread && markAsRead,
|
)
|
||||||
)
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
checkNotNull(old) { "Fetched message no longer exist!" }
|
checkNotNull(old) { "Fetched message no longer exist!" }
|
||||||
|
@ -165,19 +158,19 @@ class MessageRepository @Inject constructor(
|
||||||
recipients: List<Recipient>,
|
recipients: List<Recipient>,
|
||||||
mailbox: Mailbox,
|
mailbox: Mailbox,
|
||||||
) {
|
) {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).sendMessage(
|
||||||
.sendMessage(
|
subject = subject,
|
||||||
subject = subject,
|
content = content,
|
||||||
content = content,
|
recipients = recipients.mapFromEntities(),
|
||||||
recipients = recipients.mapFromEntities(),
|
mailboxId = mailbox.globalKey,
|
||||||
mailboxId = mailbox.globalKey,
|
)
|
||||||
)
|
|
||||||
refreshFolders(student, mailbox, listOf(SENT))
|
refreshFolders(student, mailbox, listOf(SENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).restoreMessages(
|
||||||
.restoreMessages(messages = messages.map { it.messageGlobalKey })
|
messages = messages.map { it.messageGlobalKey },
|
||||||
|
)
|
||||||
|
|
||||||
refreshFolders(student, mailbox)
|
refreshFolders(student, mailbox)
|
||||||
}
|
}
|
||||||
|
@ -188,11 +181,10 @@ class MessageRepository @Inject constructor(
|
||||||
|
|
||||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||||
val firstMessage = messages.first()
|
val firstMessage = messages.first()
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).deleteMessages(
|
||||||
.deleteMessages(
|
messages = messages.map { it.messageGlobalKey },
|
||||||
messages = messages.map { it.messageGlobalKey },
|
removeForever = firstMessage.folderId == TRASHED.id,
|
||||||
removeForever = firstMessage.folderId == TRASHED.id,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if (firstMessage.folderId != TRASHED.id) {
|
if (firstMessage.folderId != TRASHED.id) {
|
||||||
val deletedMessages = messages.map {
|
val deletedMessages = messages.map {
|
||||||
|
@ -237,9 +229,7 @@ class MessageRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
|
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student)
|
sdk.init(student).getMailboxes().mapToEntities(student)
|
||||||
.getMailboxes()
|
|
||||||
.mapToEntities(student)
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
mailboxDao.deleteAll(old uniqueSubtract new)
|
mailboxDao.deleteAll(old uniqueSubtract new)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
|
@ -9,8 +8,11 @@ import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
|
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -19,7 +21,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class MobileDeviceRepository @Inject constructor(
|
class MobileDeviceRepository @Inject constructor(
|
||||||
private val mobileDb: MobileDeviceDao,
|
private val mobileDb: MobileDeviceDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -38,30 +40,32 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { mobileDb.loadAll(student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getRegisteredDevices()
|
.getRegisteredDevices()
|
||||||
.mapToEntities(student)
|
.mapToEntities(student)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
mobileDb.removeOldAndSaveNew(
|
mobileDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
mobileDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
|
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.unregisterDevice(device.deviceId)
|
.unregisterDevice(device.deviceId)
|
||||||
|
|
||||||
mobileDb.deleteAll(listOf(device))
|
mobileDb.deleteAll(listOf(device))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
|
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
|
||||||
return wulkanowySdkFactory.create(student, semester)
|
return sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getToken()
|
.getToken()
|
||||||
.mapToMobileDeviceToken()
|
.mapToMobileDeviceToken()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.toLocalDate
|
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -19,7 +16,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class NoteRepository @Inject constructor(
|
class NoteRepository @Inject constructor(
|
||||||
private val noteDb: NoteDao,
|
private val noteDb: NoteDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -43,21 +40,20 @@ class NoteRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getNotes()
|
.getNotes()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
val notesToAdd = (new uniqueSubtract old).onEach {
|
noteDb.deleteAll(old uniqueSubtract new)
|
||||||
|
noteDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
if (it.date >= student.registrationDate.toLocalDate()) it.apply {
|
if (it.date >= student.registrationDate.toLocalDate()) it.apply {
|
||||||
isRead = false
|
isRead = false
|
||||||
if (notify) isNotified = false
|
if (notify) isNotified = false
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
noteDb.removeOldAndSaveNew(
|
|
||||||
oldItems = old uniqueSubtract new,
|
|
||||||
newItems = notesToAdd,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,13 +9,10 @@ import com.fredporciuncula.flow.preferences.Preference
|
||||||
import com.fredporciuncula.flow.preferences.Serializer
|
import com.fredporciuncula.flow.preferences.Serializer
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.api.models.Mapping
|
|
||||||
import io.github.wulkanowy.data.enums.AppTheme
|
import io.github.wulkanowy.data.enums.AppTheme
|
||||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
|
|
||||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||||
import io.github.wulkanowy.data.enums.GradeExpandMode
|
import io.github.wulkanowy.data.enums.GradeExpandMode
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode
|
import io.github.wulkanowy.data.enums.GradeSortingMode
|
||||||
import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode
|
|
||||||
import io.github.wulkanowy.data.enums.TimetableGapsMode
|
import io.github.wulkanowy.data.enums.TimetableGapsMode
|
||||||
import io.github.wulkanowy.data.enums.TimetableMode
|
import io.github.wulkanowy.data.enums.TimetableMode
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
|
@ -44,27 +41,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
R.bool.pref_default_attendance_present
|
R.bool.pref_default_attendance_present
|
||||||
)
|
)
|
||||||
|
|
||||||
val targetAttendanceFlow: Flow<Int>
|
|
||||||
get() = flowSharedPref.getInt(
|
|
||||||
context.getString(R.string.pref_key_attendance_target),
|
|
||||||
context.resources.getInteger(R.integer.pref_default_attendance_target)
|
|
||||||
).asFlow()
|
|
||||||
|
|
||||||
val attendanceCalculatorSortingModeFlow: Flow<AttendanceCalculatorSortingMode>
|
|
||||||
get() = flowSharedPref.getString(
|
|
||||||
context.getString(R.string.pref_key_attendance_calculator_sorting_mode),
|
|
||||||
context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode)
|
|
||||||
).asFlow().map(AttendanceCalculatorSortingMode::getByValue)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0).
|
|
||||||
*/
|
|
||||||
val attendanceCalculatorShowEmptySubjects: Flow<Boolean>
|
|
||||||
get() = flowSharedPref.getBoolean(
|
|
||||||
context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects),
|
|
||||||
context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects)
|
|
||||||
).asFlow()
|
|
||||||
|
|
||||||
private val gradeAverageModePref: Preference<GradeAverageMode>
|
private val gradeAverageModePref: Preference<GradeAverageMode>
|
||||||
get() = getObjectFlow(
|
get() = getObjectFlow(
|
||||||
R.string.pref_key_grade_average_mode,
|
R.string.pref_key_grade_average_mode,
|
||||||
|
@ -215,12 +191,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode
|
|
||||||
get() = getString(
|
|
||||||
R.string.pref_key_timetable_show_additional_lessons,
|
|
||||||
R.string.pref_default_timetable_show_additional_lessons
|
|
||||||
).let { ShowAdditionalLessonsMode.getByValue(it) }
|
|
||||||
|
|
||||||
val gradeSortingMode: GradeSortingMode
|
val gradeSortingMode: GradeSortingMode
|
||||||
get() = GradeSortingMode.getByValue(
|
get() = GradeSortingMode.getByValue(
|
||||||
getString(
|
getString(
|
||||||
|
@ -376,15 +346,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
||||||
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
|
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
|
||||||
|
|
||||||
var mapping: Mapping?
|
|
||||||
get() {
|
|
||||||
val value = sharedPref.getString("mapping", null)
|
|
||||||
return value?.let { json.decodeFromString(it) }
|
|
||||||
}
|
|
||||||
set(value) = sharedPref.edit(commit = true) {
|
|
||||||
putString("mapping", value?.let { json.encodeToString(it) })
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (installationId.isEmpty()) {
|
if (installationId.isEmpty()) {
|
||||||
installationId = UUID.randomUUID().toString()
|
installationId = UUID.randomUUID().toString()
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
import io.github.wulkanowy.data.db.entities.*
|
||||||
import io.github.wulkanowy.data.db.entities.MailboxType
|
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -17,22 +14,19 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class RecipientRepository @Inject constructor(
|
class RecipientRepository @Inject constructor(
|
||||||
private val recipientDb: RecipientDao,
|
private val recipientDb: RecipientDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val cacheKey = "recipient"
|
private val cacheKey = "recipient"
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
|
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
|
||||||
val new = wulkanowySdkFactory.create(student)
|
val new = sdk.init(student).getRecipients(mailbox.globalKey)
|
||||||
.getRecipients(mailbox.globalKey)
|
|
||||||
.mapToEntities(mailbox.globalKey)
|
.mapToEntities(mailbox.globalKey)
|
||||||
val old = recipientDb.loadAll(type, mailbox.globalKey)
|
val old = recipientDb.loadAll(type, mailbox.globalKey)
|
||||||
|
|
||||||
recipientDb.removeOldAndSaveNew(
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
|
@ -60,7 +54,7 @@ class RecipientRepository @Inject constructor(
|
||||||
): List<Recipient> {
|
): List<Recipient> {
|
||||||
mailbox ?: return emptyList()
|
mailbox ?: return emptyList()
|
||||||
|
|
||||||
return wulkanowySdkFactory.create(student)
|
return sdk.init(student)
|
||||||
.getMessageReplayDetails(message.messageGlobalKey)
|
.getMessageReplayDetails(message.messageGlobalKey)
|
||||||
.sender
|
.sender
|
||||||
.let(::listOf)
|
.let(::listOf)
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class RecoverRepository @Inject constructor(
|
class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> =
|
suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> {
|
||||||
wulkanowySdkFactory.create()
|
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||||
.getPasswordResetCaptchaCode(host, symbol)
|
}
|
||||||
|
|
||||||
suspend fun sendRecoverRequest(
|
suspend fun sendRecoverRequest(
|
||||||
url: String,
|
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||||
symbol: String,
|
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||||
email: String,
|
|
||||||
reCaptchaResponse: String
|
|
||||||
): String = wulkanowySdkFactory.create()
|
|
||||||
.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -17,7 +18,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SchoolAnnouncementRepository @Inject constructor(
|
class SchoolAnnouncementRepository @Inject constructor(
|
||||||
private val schoolAnnouncementDb: SchoolAnnouncementDao,
|
private val schoolAnnouncementDb: SchoolAnnouncementDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -37,27 +38,26 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(student.studentId)
|
schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
val sdk = wulkanowySdkFactory.create(student)
|
sdk.init(student)
|
||||||
val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student)
|
.getDirectorInformation()
|
||||||
val directorInformation = sdk.getDirectorInformation().mapToEntities(student)
|
.mapToEntities(student)
|
||||||
lastAnnouncements + directorInformation
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
schoolAnnouncementDb.removeOldAndSaveNew(
|
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
|
||||||
oldItems = old uniqueSubtract new,
|
if (notify) it.isNotified = false
|
||||||
newItems = (new uniqueSubtract old).onEach {
|
}
|
||||||
if (notify) it.isNotified = false
|
|
||||||
},
|
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||||
)
|
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -15,7 +17,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SchoolRepository @Inject constructor(
|
class SchoolRepository @Inject constructor(
|
||||||
private val schoolDb: SchoolDao,
|
private val schoolDb: SchoolDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -38,16 +40,17 @@ class SchoolRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getSchool()
|
.getSchool()
|
||||||
.mapToEntity(semester)
|
.mapToEntity(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (old != null && new != old) {
|
if (old != null && new != old) {
|
||||||
schoolDb.removeOldAndSaveNew(
|
with(schoolDb) {
|
||||||
oldItems = listOf(old),
|
deleteAll(listOf(old))
|
||||||
newItems = listOf(new)
|
insertAll(listOf(new))
|
||||||
)
|
}
|
||||||
} else if (old == null) {
|
} else if (old == null) {
|
||||||
schoolDb.insertAll(listOf(new))
|
schoolDb.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
import io.github.wulkanowy.data.api.SchoolsService
|
||||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
import io.github.wulkanowy.data.pojos.LoginEvent
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.utils.IntegrityHelper
|
import io.github.wulkanowy.utils.IntegrityHelper
|
||||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -21,7 +23,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
class SchoolsRepository @Inject constructor(
|
class SchoolsRepository @Inject constructor(
|
||||||
private val integrityHelper: IntegrityHelper,
|
private val integrityHelper: IntegrityHelper,
|
||||||
private val schoolsService: SchoolsService,
|
private val schoolsService: SchoolsService,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun logSchoolLogin(loginData: LoginData, students: List<StudentWithSemesters>) {
|
suspend fun logSchoolLogin(loginData: LoginData, students: List<StudentWithSemesters>) {
|
||||||
|
@ -38,9 +40,10 @@ class SchoolsRepository @Inject constructor(
|
||||||
private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) {
|
private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) {
|
||||||
val requestId = UUID.randomUUID().toString()
|
val requestId = UUID.randomUUID().toString()
|
||||||
val token = integrityHelper.getIntegrityToken(requestId) ?: return
|
val token = integrityHelper.getIntegrityToken(requestId) ?: return
|
||||||
val updatedStudent = student.copy(password = loginData.password)
|
|
||||||
|
|
||||||
val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester)
|
val schoolInfo = sdk
|
||||||
|
.init(student.copy(password = loginData.password))
|
||||||
|
.switchSemester(semester)
|
||||||
.getSchool()
|
.getSchool()
|
||||||
|
|
||||||
schoolsService.logLoginEvent(
|
schoolsService.logLoginEvent(
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
|
||||||
import io.github.wulkanowy.utils.isCurrent
|
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -18,8 +14,8 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SemesterRepository @Inject constructor(
|
class SemesterRepository @Inject constructor(
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getSemesters(
|
suspend fun getSemesters(
|
||||||
|
@ -49,7 +45,6 @@ class SemesterRepository @Inject constructor(
|
||||||
0 == it.diaryId && 0 == it.kindergartenDiaryId
|
0 == it.diaryId && 0 == it.kindergartenDiaryId
|
||||||
} == true
|
} == true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,20 +55,12 @@ class SemesterRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshSemesters(student: Student) {
|
private suspend fun refreshSemesters(student: Student) {
|
||||||
val new = wulkanowySdkFactory.create(student)
|
val new = sdk.init(student).getSemesters().mapToEntities(student.studentId)
|
||||||
.getSemesters()
|
if (new.isEmpty()) return Timber.i("Empty semester list!")
|
||||||
.mapToEntities(student.studentId)
|
|
||||||
|
|
||||||
if (new.isEmpty()) {
|
|
||||||
Timber.i("Empty semester list from SDK!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val old = semesterDb.loadAll(student.studentId, student.classId)
|
val old = semesterDb.loadAll(student.studentId, student.classId)
|
||||||
semesterDb.removeOldAndSaveNew(
|
semesterDb.deleteAll(old.uniqueSubtract(new))
|
||||||
oldItems = old uniqueSubtract new,
|
semesterDb.insertSemesters(new.uniqueSubtract(old))
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
import io.github.wulkanowy.data.db.dao.StudentInfoDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -13,7 +15,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class StudentInfoRepository @Inject constructor(
|
class StudentInfoRepository @Inject constructor(
|
||||||
private val studentInfoDao: StudentInfoDao,
|
private val studentInfoDao: StudentInfoDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
@ -28,16 +30,16 @@ class StudentInfoRepository @Inject constructor(
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
.getStudentInfo()
|
.switchSemester(semester)
|
||||||
.mapToEntity(semester)
|
.getStudentInfo().mapToEntity(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (old != null && new != old) {
|
if (old != null && new != old) {
|
||||||
studentInfoDao.removeOldAndSaveNew(
|
with(studentInfoDao) {
|
||||||
oldItems = listOf(old),
|
deleteAll(listOf(old))
|
||||||
newItems = listOf(new),
|
insertAll(listOf(new))
|
||||||
)
|
}
|
||||||
} else if (old == null) {
|
} else if (old == null) {
|
||||||
studentInfoDao.insertAll(listOf(new))
|
studentInfoDao.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
|
|
||||||
import io.github.wulkanowy.data.db.entities.StudentName
|
import io.github.wulkanowy.data.db.entities.StudentName
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
|
||||||
import io.github.wulkanowy.data.mappers.mapToPojo
|
import io.github.wulkanowy.data.mappers.mapToPojo
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.security.Scrambler
|
import io.github.wulkanowy.utils.security.Scrambler
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -28,7 +26,7 @@ class StudentRepository @Inject constructor(
|
||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider,
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val appDatabase: AppDatabase,
|
private val appDatabase: AppDatabase,
|
||||||
private val scrambler: Scrambler,
|
private val scrambler: Scrambler,
|
||||||
) {
|
) {
|
||||||
|
@ -39,10 +37,9 @@ class StudentRepository @Inject constructor(
|
||||||
pin: String,
|
pin: String,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
token: String
|
token: String
|
||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = sdk
|
||||||
.getStudentsFromHebe(token, pin, symbol, "")
|
.getStudentsFromHebe(token, pin, symbol, "")
|
||||||
.mapToPojo(null)
|
.mapToPojo(null)
|
||||||
.also { it.logErrors() }
|
|
||||||
|
|
||||||
suspend fun getUserSubjectsFromScrapper(
|
suspend fun getUserSubjectsFromScrapper(
|
||||||
email: String,
|
email: String,
|
||||||
|
@ -50,20 +47,18 @@ class StudentRepository @Inject constructor(
|
||||||
scrapperBaseUrl: String,
|
scrapperBaseUrl: String,
|
||||||
domainSuffix: String,
|
domainSuffix: String,
|
||||||
symbol: String
|
symbol: String
|
||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = sdk
|
||||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
||||||
.mapToPojo(password)
|
.mapToPojo(password)
|
||||||
.also { it.logErrors() }
|
|
||||||
|
|
||||||
suspend fun getStudentsHybrid(
|
suspend fun getStudentsHybrid(
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
scrapperBaseUrl: String,
|
scrapperBaseUrl: String,
|
||||||
symbol: String
|
symbol: String
|
||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = sdk
|
||||||
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||||
.mapToPojo(password)
|
.mapToPojo(password)
|
||||||
.also { it.logErrors() }
|
|
||||||
|
|
||||||
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
||||||
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
||||||
|
@ -105,46 +100,6 @@ class StudentRepository @Inject constructor(
|
||||||
return student
|
return student
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateCurrentStudentAuthStatus() {
|
|
||||||
Timber.i("Check isAuthorized: started")
|
|
||||||
val student = getCurrentStudent()
|
|
||||||
if (student.isAuthorized) {
|
|
||||||
Timber.i("Check isAuthorized: already authorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val initializedSdk = wulkanowySdkFactory.create(student)
|
|
||||||
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
|
||||||
.onFailure { Timber.e(it, "Check isAuthorized: error occurred") }
|
|
||||||
.getOrNull()
|
|
||||||
|
|
||||||
if (newCurrentStudent == null) {
|
|
||||||
Timber.d("Check isAuthorized: current user is null")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId)
|
|
||||||
if (currentStudentSemesters.isEmpty()) {
|
|
||||||
Timber.d("Check isAuthorized: apply empty semesters workaround")
|
|
||||||
semesterDb.insertSemesters(
|
|
||||||
items = newCurrentStudent.semesters.mapToEntities(student.studentId),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newCurrentStudent.isAuthorized) {
|
|
||||||
Timber.i("Check isAuthorized: authorization required")
|
|
||||||
throw NoAuthorizationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
val studentIsAuthorized = StudentIsAuthorized(
|
|
||||||
id = student.id,
|
|
||||||
isAuthorized = true
|
|
||||||
)
|
|
||||||
|
|
||||||
Timber.i("Check isAuthorized: already authorized, update local status")
|
|
||||||
studentDb.update(studentIsAuthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
|
@ -194,24 +149,20 @@ class StudentRepository @Inject constructor(
|
||||||
.distinctBy { it.student.studentName }.size == 1
|
.distinctBy { it.student.studentName }.size == 1
|
||||||
|
|
||||||
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.authorizePermission(pesel)
|
.authorizePermission(pesel)
|
||||||
|
|
||||||
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
|
suspend fun refreshStudentName(student: Student, semester: Semester) {
|
||||||
val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
|
val newCurrentApiStudent = sdk.init(student)
|
||||||
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
|
.switchSemester(semester)
|
||||||
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
|
.getCurrentStudent() ?: return
|
||||||
.getOrNull() ?: return
|
|
||||||
|
|
||||||
val studentName = StudentName(
|
val studentName = StudentName(
|
||||||
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
|
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
|
||||||
).apply { id = student.id }
|
).apply { id = student.id }
|
||||||
|
|
||||||
studentDb.update(studentName)
|
studentDb.update(studentName)
|
||||||
semesterDb.removeOldAndSaveNew(
|
|
||||||
oldItems = semesterDb.loadAll(student.studentId, semester.classId),
|
|
||||||
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
||||||
|
@ -224,18 +175,4 @@ class StudentRepository @Inject constructor(
|
||||||
appDatabase.clearAllTables()
|
appDatabase.clearAllTables()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RegisterUser.logErrors() {
|
|
||||||
val symbolsErrors = symbols.filter { it.error != null }
|
|
||||||
.map { it.error }
|
|
||||||
val unitsErrors = symbols.flatMap { it.schools }
|
|
||||||
.filter { it.error != null }
|
|
||||||
.map { it.error }
|
|
||||||
|
|
||||||
(symbolsErrors + unitsErrors).forEach { error ->
|
|
||||||
Timber.e(error, "Error occurred while fetching students")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoAuthorizationException : Exception()
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.SubjectDao
|
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -16,7 +18,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SubjectRepository @Inject constructor(
|
class SubjectRepository @Inject constructor(
|
||||||
private val subjectDao: SubjectDao,
|
private val subjectDao: SubjectDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -37,15 +39,15 @@ class SubjectRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getSubjects()
|
.getSubjects()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.removeOldAndSaveNew(
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
subjectDao.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.TeacherDao
|
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -16,7 +18,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class TeacherRepository @Inject constructor(
|
class TeacherRepository @Inject constructor(
|
||||||
private val teacherDb: TeacherDao,
|
private val teacherDb: TeacherDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -37,15 +39,15 @@ class TeacherRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getTeachers()
|
.getTeachers()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
teacherDb.removeOldAndSaveNew(
|
teacherDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
teacherDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||||
|
@ -12,13 +11,14 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
import io.github.wulkanowy.data.pojos.TimetableFull
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
|
|
||||||
import io.github.wulkanowy.utils.AppWidgetUpdater
|
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.sunday
|
||||||
|
import io.github.wulkanowy.utils.switchSemester
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
@ -28,16 +28,14 @@ import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class TimetableRepository @Inject constructor(
|
class TimetableRepository @Inject constructor(
|
||||||
private val timetableDb: TimetableDao,
|
private val timetableDb: TimetableDao,
|
||||||
private val timetableAdditionalDb: TimetableAdditionalDao,
|
private val timetableAdditionalDb: TimetableAdditionalDao,
|
||||||
private val timetableHeaderDb: TimetableHeaderDao,
|
private val timetableHeaderDb: TimetableHeaderDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val sdk: Sdk,
|
||||||
private val schedulerHelper: TimetableNotificationSchedulerHelper,
|
private val schedulerHelper: TimetableNotificationSchedulerHelper,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
private val appWidgetUpdater: AppWidgetUpdater,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
@ -56,8 +54,7 @@ class TimetableRepository @Inject constructor(
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
refreshAdditional: Boolean = false,
|
refreshAdditional: Boolean = false,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
timetableType: TimetableType = TimetableType.NORMAL,
|
timetableType: TimetableType = TimetableType.NORMAL
|
||||||
isFromAppWidget: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
isResultEmpty = {
|
isResultEmpty = {
|
||||||
|
@ -77,7 +74,8 @@ class TimetableRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
val timetableFull = wulkanowySdkFactory.create(student, semester)
|
val timetableFull = sdk.init(student)
|
||||||
|
.switchSemester(semester)
|
||||||
.getTimetable(start.monday, end.sunday)
|
.getTimetable(start.monday, end.sunday)
|
||||||
|
|
||||||
timetableFull.mapToEntities(semester)
|
timetableFull.mapToEntities(semester)
|
||||||
|
@ -88,9 +86,6 @@ class TimetableRepository @Inject constructor(
|
||||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
if (!isFromAppWidget) {
|
|
||||||
appWidgetUpdater.updateAllAppWidgetsByProvider(TimetableWidgetProvider::class)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
filterResult = { (timetable, additional, headers) ->
|
filterResult = { (timetable, additional, headers) ->
|
||||||
TimetableFull(
|
TimetableFull(
|
||||||
|
@ -159,10 +154,8 @@ class TimetableRepository @Inject constructor(
|
||||||
new.apply { if (notify) isNotified = false }
|
new.apply { if (notify) isNotified = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
timetableDb.removeOldAndSaveNew(
|
timetableDb.deleteAll(lessonsToRemove)
|
||||||
oldItems = lessonsToRemove,
|
timetableDb.insertAll(lessonsToAdd)
|
||||||
newItems = lessonsToAdd,
|
|
||||||
)
|
|
||||||
|
|
||||||
schedulerHelper.cancelScheduled(lessonsToRemove, student)
|
schedulerHelper.cancelScheduled(lessonsToRemove, student)
|
||||||
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
|
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
|
||||||
|
@ -173,17 +166,13 @@ class TimetableRepository @Inject constructor(
|
||||||
new: List<TimetableAdditional>
|
new: List<TimetableAdditional>
|
||||||
) {
|
) {
|
||||||
val oldFiltered = old.filter { !it.isAddedByUser }
|
val oldFiltered = old.filter { !it.isAddedByUser }
|
||||||
timetableAdditionalDb.removeOldAndSaveNew(
|
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
|
||||||
oldItems = oldFiltered uniqueSubtract new,
|
timetableAdditionalDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
|
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
|
||||||
timetableHeaderDb.removeOldAndSaveNew(
|
timetableHeaderDb.deleteAll(old uniqueSubtract new)
|
||||||
oldItems = old uniqueSubtract new,
|
timetableHeaderDb.insertAll(new uniqueSubtract old)
|
||||||
newItems = new uniqueSubtract old,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
|
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.Resource
|
|
||||||
import io.github.wulkanowy.data.api.models.Mapping
|
|
||||||
import io.github.wulkanowy.data.api.services.WulkanowyService
|
|
||||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.filterNot
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.time.LocalDate
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
private val endDate = LocalDate.of(2024, 6, 25)
|
|
||||||
val isEndDateReached = LocalDate.now() >= endDate
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class WulkanowyRepository @Inject constructor(
|
|
||||||
private val wulkanowyService: WulkanowyService,
|
|
||||||
private val adminMessageDao: AdminMessageDao,
|
|
||||||
private val preferencesRepository: PreferencesRepository,
|
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
|
||||||
private val cacheKey = "mapping_refresh_key"
|
|
||||||
|
|
||||||
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
|
|
||||||
networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
|
||||||
isResultEmpty = { false },
|
|
||||||
query = { adminMessageDao.loadAll() },
|
|
||||||
fetch = { wulkanowyService.getAdminMessages() },
|
|
||||||
shouldFetch = { true },
|
|
||||||
saveFetchResult = { oldItems, newItems ->
|
|
||||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.filterNot { it is Resource.Intermediate }
|
|
||||||
|
|
||||||
suspend fun getMapping(): Mapping? {
|
|
||||||
var savedMapping = preferencesRepository.mapping
|
|
||||||
|
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
|
||||||
key = getRefreshKey(cacheKey)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (savedMapping == null || isExpired) {
|
|
||||||
fetchMapping()
|
|
||||||
savedMapping = preferencesRepository.mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
return savedMapping
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun fetchMapping() {
|
|
||||||
runCatching { wulkanowyService.getMapping() }
|
|
||||||
.onFailure { Timber.e(it) }
|
|
||||||
.onSuccess {
|
|
||||||
preferencesRepository.mapping = it
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(cacheKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package io.github.wulkanowy.data.serializers
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.enums.MessageType
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
object SafeMessageTypeEnumListSerializer : KSerializer<List<MessageType>> {
|
|
||||||
|
|
||||||
private val serializer = ListSerializer(String.serializer())
|
|
||||||
|
|
||||||
override val descriptor = serializer.descriptor
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: List<MessageType>) {
|
|
||||||
encoder.encodeNotNullMark()
|
|
||||||
serializer.serialize(encoder, value.map { it.name })
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): List<MessageType> =
|
|
||||||
serializer.deserialize(decoder).mapNotNull { enumName ->
|
|
||||||
MessageType.entries.find { it.name == enumName }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,14 +5,14 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.enums.MessageType
|
import io.github.wulkanowy.data.enums.MessageType
|
||||||
import io.github.wulkanowy.data.mapResourceData
|
import io.github.wulkanowy.data.mapResourceData
|
||||||
|
import io.github.wulkanowy.data.repositories.AdminMessageRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetAppropriateAdminMessageUseCase @Inject constructor(
|
class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||||
private val wulkanowyRepository: WulkanowyRepository,
|
private val adminMessageRepository: AdminMessageRepository,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val appInfo: AppInfo
|
private val appInfo: AppInfo
|
||||||
) {
|
) {
|
||||||
|
@ -22,7 +22,7 @@ class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
|
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
|
||||||
return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages ->
|
return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||||
adminMessages
|
adminMessages
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.isNotDismissed() }
|
.filter { it.isNotDismissed() }
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
package io.github.wulkanowy.domain.attendance
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.*
|
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.db.entities.Subject
|
|
||||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
|
|
||||||
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode.*
|
|
||||||
import io.github.wulkanowy.data.pojos.AttendanceData
|
|
||||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.SubjectRepository
|
|
||||||
import io.github.wulkanowy.utils.allAbsences
|
|
||||||
import io.github.wulkanowy.utils.allPresences
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
class GetAttendanceCalculatorDataUseCase @Inject constructor(
|
|
||||||
private val subjectRepository: SubjectRepository,
|
|
||||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
|
||||||
private val preferencesRepository: PreferencesRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
operator fun invoke(
|
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
): Flow<Resource<List<AttendanceData>>> =
|
|
||||||
subjectRepository.getSubjects(student, semester, forceRefresh)
|
|
||||||
.mapResourceData { subjects -> subjects.sortedBy(Subject::name) }
|
|
||||||
.combineWithResourceData(preferencesRepository.targetAttendanceFlow, ::Pair)
|
|
||||||
.flatMapResourceData { (subjects, targetFreq) ->
|
|
||||||
combineResourceFlows(subjects.map { subject ->
|
|
||||||
attendanceSummaryRepository.getAttendanceSummary(
|
|
||||||
student = student,
|
|
||||||
semester = semester,
|
|
||||||
subjectId = subject.realId,
|
|
||||||
forceRefresh = forceRefresh
|
|
||||||
).mapResourceData { summaries ->
|
|
||||||
summaries.toAttendanceData(subject.name, targetFreq)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Every individual combined flow causes separate network requests to update data.
|
|
||||||
// When there is N child flows, they can cause up to N-1 items to be emitted. Since all
|
|
||||||
// requests are usually completed in less than 5s, there is no need to emit multiple
|
|
||||||
// intermediates that will be visible for barely any time.
|
|
||||||
.debounceIntermediates()
|
|
||||||
}
|
|
||||||
.combineWithResourceData(preferencesRepository.attendanceCalculatorShowEmptySubjects) { attendanceDataList, showEmptySubjects ->
|
|
||||||
attendanceDataList.filter { it.total != 0 || showEmptySubjects }
|
|
||||||
}
|
|
||||||
.combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List<AttendanceData>::sortedBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<AttendanceSummary>.toAttendanceData(subjectName: String, targetFreq: Int): AttendanceData {
|
|
||||||
val presences = sumOf { it.allPresences }
|
|
||||||
val absences = sumOf { it.allAbsences }
|
|
||||||
return AttendanceData(
|
|
||||||
subjectName = subjectName,
|
|
||||||
lessonBalance = calcLessonBalance(
|
|
||||||
targetFreq.toDouble() / 100, presences, absences
|
|
||||||
),
|
|
||||||
presences = presences,
|
|
||||||
absences = absences,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calcLessonBalance(targetFreq: Double, presences: Int, absences: Int): Int {
|
|
||||||
val total = presences + absences
|
|
||||||
// The `+ 1` is to avoid false positives in close cases. Eg.:
|
|
||||||
// target frequency 99%, 1 presence. Without the `+ 1` this would be reported shown as
|
|
||||||
// a positive balance of +1, however that is not actually true as skipping one class
|
|
||||||
// would make it so that the balance would actually be negative (-98). The `+ 1`
|
|
||||||
// fixes this and makes sure that in situations like these, it's not reporting incorrect
|
|
||||||
// balances
|
|
||||||
return when {
|
|
||||||
presences / (total + 1f) >= targetFreq -> calcMissingAbsences(
|
|
||||||
targetFreq, absences, presences
|
|
||||||
)
|
|
||||||
presences / (total + 0f) < targetFreq -> -calcMissingPresences(
|
|
||||||
targetFreq, absences, presences
|
|
||||||
)
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calcMissingPresences(targetFreq: Double, absences: Int, presences: Int) =
|
|
||||||
calcMinRequiredPresencesFor(targetFreq, absences) - presences
|
|
||||||
|
|
||||||
private fun calcMinRequiredPresencesFor(targetFreq: Double, absences: Int) =
|
|
||||||
ceil((targetFreq / (1 - targetFreq)) * absences).toInt()
|
|
||||||
|
|
||||||
private fun calcMissingAbsences(targetFreq: Double, absences: Int, presences: Int) =
|
|
||||||
calcMinRequiredAbsencesFor(targetFreq, presences) - absences
|
|
||||||
|
|
||||||
private fun calcMinRequiredAbsencesFor(targetFreq: Double, presences: Int) =
|
|
||||||
floor((presences * (1 - targetFreq)) / targetFreq).toInt()
|
|
||||||
|
|
||||||
private fun List<AttendanceData>.sortedBy(mode: AttendanceCalculatorSortingMode) = when (mode) {
|
|
||||||
ALPHABETIC -> sortedBy(AttendanceData::subjectName)
|
|
||||||
ATTENDANCE -> sortedByDescending(AttendanceData::presencePercentage)
|
|
||||||
LESSON_BALANCE -> sortedBy(AttendanceData::lessonBalance)
|
|
||||||
}
|
|
|
@ -59,7 +59,7 @@ class GetMailboxByStudentUseCase @Inject constructor(
|
||||||
private fun String.getUnauthorizedVersion(): String {
|
private fun String.getUnauthorizedVersion(): String {
|
||||||
return normalizeStudentName().split(" ")
|
return normalizeStudentName().split(" ")
|
||||||
.joinToString(" ") {
|
.joinToString(" ") {
|
||||||
it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0))
|
it.first() + "*".repeat(it.length - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,19 @@ import android.os.Build.VERSION.SDK_INT
|
||||||
import android.os.Build.VERSION_CODES.O
|
import android.os.Build.VERSION_CODES.O
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
|
import androidx.work.*
|
||||||
import androidx.work.BackoffPolicy.EXPONENTIAL
|
import androidx.work.BackoffPolicy.EXPONENTIAL
|
||||||
import androidx.work.Constraints
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
|
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.NetworkType.CONNECTED
|
import androidx.work.NetworkType.CONNECTED
|
||||||
import androidx.work.NetworkType.UNMETERED
|
import androidx.work.NetworkType.UNMETERED
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
|
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.services.sync.channels.Channel
|
import io.github.wulkanowy.services.sync.channels.Channel
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
import io.github.wulkanowy.utils.isHolidays
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import java.util.concurrent.TimeUnit.MINUTES
|
import java.util.concurrent.TimeUnit.MINUTES
|
||||||
|
@ -42,9 +34,7 @@ class SyncManager @Inject constructor(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (now().isHolidays || isEndDateReached) {
|
if (now().isHolidays) stopSyncWorker()
|
||||||
stopSyncWorker()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDK_INT >= O) {
|
if (SDK_INT >= O) {
|
||||||
channels.forEach { it.create() }
|
channels.forEach { it.create() }
|
||||||
|
@ -60,7 +50,7 @@ class SyncManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays && isEndDateReached) {
|
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||||
val serviceInterval = preferencesRepository.servicesInterval
|
val serviceInterval = preferencesRepository.servicesInterval
|
||||||
|
|
||||||
workManager.enqueueUniquePeriodicWork(
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
@ -80,10 +70,6 @@ class SyncManager @Inject constructor(
|
||||||
|
|
||||||
// if quiet, no notifications will be sent
|
// if quiet, no notifications will be sent
|
||||||
fun startOneTimeSyncWorker(quiet: Boolean = false): Flow<WorkInfo?> {
|
fun startOneTimeSyncWorker(quiet: Boolean = false): Flow<WorkInfo?> {
|
||||||
if (isEndDateReached) {
|
|
||||||
return flowOf(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||||
.setInputData(
|
.setInputData(
|
||||||
Data.Builder()
|
Data.Builder()
|
||||||
|
|
|
@ -15,10 +15,8 @@ import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException
|
|
||||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.works.Work
|
import io.github.wulkanowy.services.sync.works.Work
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
|
@ -43,16 +41,13 @@ class SyncWorker @AssistedInject constructor(
|
||||||
override suspend fun doWork(): Result = withContext(dispatchersProvider.io) {
|
override suspend fun doWork(): Result = withContext(dispatchersProvider.io) {
|
||||||
Timber.i("SyncWorker is starting")
|
Timber.i("SyncWorker is starting")
|
||||||
|
|
||||||
if (!studentRepository.isCurrentStudentSet() || isEndDateReached) {
|
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
|
||||||
return@withContext Result.failure()
|
|
||||||
}
|
|
||||||
|
|
||||||
val (student, semester) = try {
|
val (student, semester) = try {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||||
student to semester
|
student to semester
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.e(e)
|
|
||||||
return@withContext getResultFromErrors(listOf(e))
|
return@withContext getResultFromErrors(listOf(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +59,7 @@ class SyncWorker @AssistedInject constructor(
|
||||||
null
|
null
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) {
|
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
@ -94,7 +89,6 @@ class SyncWorker @AssistedInject constructor(
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.isNotEmpty() -> Result.retry()
|
errors.isNotEmpty() -> Result.retry()
|
||||||
else -> {
|
else -> {
|
||||||
preferencesRepository.lasSyncDate = Instant.now()
|
preferencesRepository.lasSyncDate = Instant.now()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
@ -18,8 +17,6 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import timber.log.Timber
|
|
||||||
import java.time.Instant
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
|
@ -39,26 +36,16 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
|
|
||||||
abstract var presenter: T
|
abstract var presenter: T
|
||||||
|
|
||||||
private var lastDialogOpenTime = mutableMapOf<String, Instant>()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
inject()
|
inject()
|
||||||
themeManager.applyActivityTheme(this)
|
themeManager.applyActivityTheme(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||||
applyCustomTaskDescription()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun applyCustomTaskDescription() {
|
setTaskDescription(
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return
|
ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))
|
||||||
try {
|
)
|
||||||
val newColor = getThemeAttrColor(R.attr.colorSurface)
|
|
||||||
val taskDescription = ActivityManager.TaskDescription(null, null, newColor)
|
|
||||||
setTaskDescription(taskDescription)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showError(text: String, error: Throwable) {
|
override fun showError(text: String, error: Throwable) {
|
||||||
|
@ -83,8 +70,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredCredentialsDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.main_expired_credentials_title)
|
.setTitle(R.string.main_expired_credentials_title)
|
||||||
.setMessage(R.string.main_expired_credentials_description)
|
.setMessage(R.string.main_expired_credentials_description)
|
||||||
|
@ -98,8 +83,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.main_session_expired)
|
.setTitle(R.string.main_session_expired)
|
||||||
.setMessage(R.string.main_session_relogin)
|
.setMessage(R.string.main_session_relogin)
|
||||||
|
@ -136,21 +119,4 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
protected open fun inject() {
|
protected open fun inject() {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldShowDialog(name: String): Boolean {
|
|
||||||
val lastOpenTime = lastDialogOpenTime[name]
|
|
||||||
val now = Instant.now()
|
|
||||||
|
|
||||||
if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) {
|
|
||||||
Timber.i("Dialog $name was shown less than a second ago. Skip")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
lastDialogOpenTime[name] = Instant.now()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials"
|
|
||||||
private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.base
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.data.repositories.NoAuthorizationException
|
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
|
@ -40,7 +40,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||||
is ScramblerException -> onDecryptionFailed()
|
is ScramblerException -> onDecryptionFailed()
|
||||||
is BadCredentialsException -> onExpiredCredentials()
|
is BadCredentialsException -> onExpiredCredentials()
|
||||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||||
is NoAuthorizationException -> onAuthorizationRequired()
|
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||||
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
|
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -34,17 +33,17 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
val context = holder.binding.root.context
|
|
||||||
val item = items[position]
|
val item = items[position]
|
||||||
|
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
attendanceItemNumber.text = item.number.toString()
|
attendanceItemNumber.text = item.number.toString()
|
||||||
attendanceItemSubject.text = item.subject
|
attendanceItemSubject.text = item.subject.ifBlank {
|
||||||
.ifBlank { context.getString(R.string.all_no_data) }
|
root.context.getString(R.string.all_no_data)
|
||||||
|
}
|
||||||
attendanceItemDescription.setText(item.descriptionRes)
|
attendanceItemDescription.setText(item.descriptionRes)
|
||||||
|
|
||||||
attendanceItemDescription.setTextColor(
|
attendanceItemDescription.setTextColor(
|
||||||
context.getThemeAttrColor(
|
root.context.getThemeAttrColor(
|
||||||
when {
|
when {
|
||||||
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||||
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||||
|
@ -62,15 +61,13 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
attendanceItemAlert.isVisible =
|
attendanceItemAlert.isVisible =
|
||||||
item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
|
item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
|
||||||
|
|
||||||
attendanceItemAlert.imageTintList = ColorStateList.valueOf(
|
attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
|
||||||
context.getThemeAttrColor(
|
when{
|
||||||
when {
|
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
||||||
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
|
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
||||||
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
|
else -> android.R.attr.colorPrimary
|
||||||
else -> android.R.attr.colorPrimary
|
}
|
||||||
}
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
attendanceItemNumber.visibility = View.GONE
|
attendanceItemNumber.visibility = View.GONE
|
||||||
attendanceItemExcuseInfo.visibility = View.GONE
|
attendanceItemExcuseInfo.visibility = View.GONE
|
||||||
attendanceItemExcuseCheckbox.visibility = View.GONE
|
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||||
|
|
|
@ -2,8 +2,14 @@ package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import android.content.DialogInterface.BUTTON_POSITIVE
|
import android.content.DialogInterface.BUTTON_POSITIVE
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
import android.view.View.*
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.INVISIBLE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -14,7 +20,6 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.databinding.DialogExcuseBinding
|
import io.github.wulkanowy.databinding.DialogExcuseBinding
|
||||||
import io.github.wulkanowy.databinding.FragmentAttendanceBinding
|
import io.github.wulkanowy.databinding.FragmentAttendanceBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.attendance.calculator.AttendanceCalculatorFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
@ -64,6 +69,8 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
val inflater = mode.menuInflater
|
val inflater = mode.menuInflater
|
||||||
inflater.inflate(R.menu.context_menu_attendance, menu)
|
inflater.inflate(R.menu.context_menu_attendance, menu)
|
||||||
|
menu.findItem(R.id.excuseMenuDaySubmit).setVisible(presenter.isWholeDayExcusable)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +86,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||||
return when (menu.itemId) {
|
return when (menu.itemId) {
|
||||||
|
R.id.excuseMenuDaySubmit -> presenter.onExcuseDayButtonClick()
|
||||||
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
|
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -135,7 +143,6 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected()
|
return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected()
|
||||||
else if (item.itemId == R.id.attendanceMenuCalculator) presenter.onCalculatorSwitchSelected()
|
|
||||||
else false
|
else false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,19 +262,22 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
|
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openCalculatorView() {
|
|
||||||
(activity as? MainActivity)?.pushView(AttendanceCalculatorFragment.newInstance())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startActionMode() {
|
override fun startActionMode() {
|
||||||
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) {
|
override fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String) {
|
||||||
val reasonFullText = getString(
|
val reasonFullText = if (lessons.isEmpty()) {
|
||||||
R.string.attendance_excuse_formula,
|
getString(
|
||||||
|
R.string.attendance_excuse_day_formula,
|
||||||
|
date,
|
||||||
|
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
||||||
|
reason.ifBlank { "" }
|
||||||
|
)
|
||||||
|
} else getString(
|
||||||
|
R.string.attendance_excuse_lessons_formula,
|
||||||
date,
|
date,
|
||||||
numbers,
|
lessons,
|
||||||
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
|
||||||
reason.ifBlank { "" }
|
reason.ifBlank { "" }
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,36 +1,16 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.flatResourceFlow
|
|
||||||
import io.github.wulkanowy.data.logResourceStatus
|
|
||||||
import io.github.wulkanowy.data.mapResourceData
|
|
||||||
import io.github.wulkanowy.data.onResourceData
|
|
||||||
import io.github.wulkanowy.data.onResourceError
|
|
||||||
import io.github.wulkanowy.data.onResourceIntermediate
|
|
||||||
import io.github.wulkanowy.data.onResourceLoading
|
|
||||||
import io.github.wulkanowy.data.onResourceNotLoading
|
|
||||||
import io.github.wulkanowy.data.onResourceSuccess
|
|
||||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.capitalise
|
|
||||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
|
||||||
import io.github.wulkanowy.utils.isExcusableOrNotExcused
|
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
|
||||||
import io.github.wulkanowy.utils.monday
|
|
||||||
import io.github.wulkanowy.utils.nextSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.previousSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.sunday
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -58,6 +38,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
private val attendanceToExcuseList = mutableListOf<Attendance>()
|
private val attendanceToExcuseList = mutableListOf<Attendance>()
|
||||||
|
var isWholeDayExcusable = false
|
||||||
|
|
||||||
private var isVulcanExcusedFunctionEnabled = false
|
private var isVulcanExcusedFunctionEnabled = false
|
||||||
|
|
||||||
|
@ -151,6 +132,12 @@ class AttendancePresenter @Inject constructor(
|
||||||
view?.startActionMode()
|
view?.startActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onExcuseDayButtonClick(): Boolean {
|
||||||
|
view?.showExcuseDialog()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
|
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
|
||||||
if (checked) attendanceToExcuseList.add(attendanceItem)
|
if (checked) attendanceToExcuseList.add(attendanceItem)
|
||||||
else attendanceToExcuseList.remove(attendanceItem)
|
else attendanceToExcuseList.remove(attendanceItem)
|
||||||
|
@ -172,7 +159,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
fun onExcuseDialogSubmit(reason: String) {
|
fun onExcuseDialogSubmit(reason: String) {
|
||||||
view?.finishActionMode()
|
view?.finishActionMode()
|
||||||
|
|
||||||
if (attendanceToExcuseList.isEmpty()) return
|
if (attendanceToExcuseList.isEmpty() && !isWholeDayExcusable) return
|
||||||
|
|
||||||
if (isVulcanExcusedFunctionEnabled) {
|
if (isVulcanExcusedFunctionEnabled) {
|
||||||
excuseAbsence(
|
excuseAbsence(
|
||||||
|
@ -183,8 +170,8 @@ class AttendancePresenter @Inject constructor(
|
||||||
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
|
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
|
||||||
|
|
||||||
view?.startSendMessageIntent(
|
view?.startSendMessageIntent(
|
||||||
date = attendanceToExcuseList[0].date,
|
date = currentDate ?: attendanceToExcuseList[0].date,
|
||||||
numbers = attendanceToExcuseNumbers.joinToString(", "),
|
lessons = attendanceToExcuseNumbers.joinToString(", "),
|
||||||
reason = reason
|
reason = reason
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -215,11 +202,6 @@ class AttendancePresenter @Inject constructor(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCalculatorSwitchSelected(): Boolean {
|
|
||||||
view?.openCalculatorView()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadData(forceRefresh: Boolean = false) {
|
private fun loadData(forceRefresh: Boolean = false) {
|
||||||
Timber.i("Loading attendance data started")
|
Timber.i("Loading attendance data started")
|
||||||
|
|
||||||
|
@ -242,7 +224,9 @@ class AttendancePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
.logResourceStatus("load attendance")
|
.logResourceStatus("load attendance")
|
||||||
.onResourceLoading {
|
.onResourceLoading {
|
||||||
view?.showExcuseButton(false)
|
view?.apply {
|
||||||
|
showExcuseButton(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.mapResourceData {
|
.mapResourceData {
|
||||||
if (prefRepository.isShowPresent) {
|
if (prefRepository.isShowPresent) {
|
||||||
|
@ -265,15 +249,16 @@ class AttendancePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onResourceIntermediate { view?.showRefresh(true) }
|
.onResourceIntermediate { view?.showRefresh(true) }
|
||||||
.onResourceSuccess {
|
.onResourceSuccess { items ->
|
||||||
isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
|
isVulcanExcusedFunctionEnabled = items.any { item -> item.excusable }
|
||||||
val anyExcusables = it.any { it.isExcusableOrNotExcused }
|
isWholeDayExcusable = items.all { it.isAbsenceExcusable }
|
||||||
|
val anyExcusables = items.any { it.isExcusableOrNotExcused }
|
||||||
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
|
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
|
||||||
|
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
"load_data",
|
"load_data",
|
||||||
"type" to "attendance",
|
"type" to "attendance",
|
||||||
"items" to it.size
|
"items" to items.size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onResourceNotLoading {
|
.onResourceNotLoading {
|
||||||
|
@ -326,7 +311,19 @@ class AttendancePresenter @Inject constructor(
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
|
if (toExcuseList.isEmpty()) {
|
||||||
|
attendanceRepository.excuseForAbsence(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
days = listOfNotNull(currentDate),
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
|
} else attendanceRepository.excuseForAbsence(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
absenceList = toExcuseList,
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Loading -> view?.run {
|
is Resource.Loading -> view?.run {
|
||||||
|
|
|
@ -56,9 +56,7 @@ interface AttendanceView : BaseView {
|
||||||
|
|
||||||
fun openSummaryView()
|
fun openSummaryView()
|
||||||
|
|
||||||
fun openCalculatorView()
|
fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String)
|
||||||
|
|
||||||
fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String)
|
|
||||||
|
|
||||||
fun startActionMode()
|
fun startActionMode()
|
||||||
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
|
||||||
|
|
||||||
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.pojos.AttendanceData
|
|
||||||
import io.github.wulkanowy.databinding.ItemAttendanceCalculatorHeaderBinding
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class AttendanceCalculatorAdapter @Inject constructor() :
|
|
||||||
RecyclerView.Adapter<AttendanceCalculatorAdapter.ViewHolder>() {
|
|
||||||
|
|
||||||
var items = emptyList<AttendanceData>()
|
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
|
||||||
ItemAttendanceCalculatorHeaderBinding.inflate(
|
|
||||||
LayoutInflater.from(parent.context), parent, false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onBindViewHolder(parent: ViewHolder, position: Int) {
|
|
||||||
val context = parent.binding.root.context
|
|
||||||
val item = items[position]
|
|
||||||
|
|
||||||
with(parent.binding) {
|
|
||||||
attendanceCalculatorPercentage.text = "${item.presencePercentage.roundToInt()}"
|
|
||||||
|
|
||||||
attendanceCalculatorSummaryBalance.text = when {
|
|
||||||
item.lessonBalance > 0 -> {
|
|
||||||
context.getString(
|
|
||||||
R.string.attendance_calculator_summary_balance_positive,
|
|
||||||
item.lessonBalance
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item.lessonBalance < 0 -> {
|
|
||||||
context.getString(
|
|
||||||
R.string.attendance_calculator_summary_balance_negative,
|
|
||||||
abs(item.lessonBalance)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> context.getString(R.string.attendance_calculator_summary_balance_neutral)
|
|
||||||
}
|
|
||||||
attendanceCalculatorWarning.isVisible = item.lessonBalance < 0
|
|
||||||
attendanceCalculatorTitle.text = item.subjectName
|
|
||||||
attendanceCalculatorSummaryValues.text = if (item.total == 0) {
|
|
||||||
context.getString(R.string.attendance_calculator_summary_values_empty)
|
|
||||||
} else {
|
|
||||||
context.getString(
|
|
||||||
R.string.attendance_calculator_summary_values,
|
|
||||||
item.presences,
|
|
||||||
item.total
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(val binding: ItemAttendanceCalculatorHeaderBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root)
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.pojos.AttendanceData
|
|
||||||
import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding
|
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
|
||||||
import io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment
|
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class AttendanceCalculatorFragment :
|
|
||||||
BaseFragment<FragmentAttendanceCalculatorBinding>(R.layout.fragment_attendance_calculator),
|
|
||||||
AttendanceCalculatorView, MainView.TitledView {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var presenter: AttendanceCalculatorPresenter
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var attendanceCalculatorAdapter: AttendanceCalculatorAdapter
|
|
||||||
|
|
||||||
override val titleStringId get() = R.string.attendance_title
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun newInstance() = AttendanceCalculatorFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty()
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding = FragmentAttendanceCalculatorBinding.bind(view)
|
|
||||||
messageContainer = binding.attendanceCalculatorRecycler
|
|
||||||
presenter.onAttachView(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.action_menu_attendance_calculator, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
return if (item.itemId == R.id.attendance_calculator_menu_settings) presenter.onSettingsSelected()
|
|
||||||
else false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openSettingsView() {
|
|
||||||
(activity as? MainActivity)?.pushView(AppearanceFragment.withFocusedPreference(getString(R.string.pref_key_attendance_target)))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() {
|
|
||||||
with(binding.attendanceCalculatorRecycler) {
|
|
||||||
layoutManager = LinearLayoutManager(context)
|
|
||||||
adapter = attendanceCalculatorAdapter
|
|
||||||
addItemDecoration(DividerItemDecoration(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
with(binding) {
|
|
||||||
attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
|
||||||
attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
|
||||||
attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(
|
|
||||||
requireContext().getThemeAttrColor(
|
|
||||||
R.attr.colorSwipeRefresh
|
|
||||||
)
|
|
||||||
)
|
|
||||||
attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() }
|
|
||||||
attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateData(data: List<AttendanceData>) {
|
|
||||||
with(attendanceCalculatorAdapter) {
|
|
||||||
items = data
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clearView() {
|
|
||||||
with(attendanceCalculatorAdapter) {
|
|
||||||
items = emptyList()
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
|
||||||
binding.attendanceCalculatorEmpty.isVisible = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
|
||||||
binding.attendanceCalculatorError.isVisible = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
|
||||||
binding.attendanceCalculatorErrorMessage.text = message
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
|
||||||
binding.attendanceCalculatorProgress.isVisible = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
|
||||||
binding.attendanceCalculatorSwipe.isEnabled = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
|
||||||
binding.attendanceCalculatorRecycler.isVisible = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showRefresh(show: Boolean) {
|
|
||||||
binding.attendanceCalculatorSwipe.isRefreshing = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
presenter.onDetachView()
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.flatResourceFlow
|
|
||||||
import io.github.wulkanowy.data.logResourceStatus
|
|
||||||
import io.github.wulkanowy.data.onResourceData
|
|
||||||
import io.github.wulkanowy.data.onResourceError
|
|
||||||
import io.github.wulkanowy.data.onResourceIntermediate
|
|
||||||
import io.github.wulkanowy.data.onResourceNotLoading
|
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
|
||||||
import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase
|
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AttendanceCalculatorPresenter @Inject constructor(
|
|
||||||
errorHandler: ErrorHandler,
|
|
||||||
studentRepository: StudentRepository,
|
|
||||||
private val semesterRepository: SemesterRepository,
|
|
||||||
private val getAttendanceCalculatorData: GetAttendanceCalculatorDataUseCase,
|
|
||||||
) : BasePresenter<AttendanceCalculatorView>(errorHandler, studentRepository) {
|
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
|
||||||
|
|
||||||
override fun onAttachView(view: AttendanceCalculatorView) {
|
|
||||||
super.onAttachView(view)
|
|
||||||
view.initView()
|
|
||||||
Timber.i("Attendance calculator view was initialized")
|
|
||||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSwipeRefresh() {
|
|
||||||
Timber.i("Force refreshing the attendance calculator")
|
|
||||||
loadData(forceRefresh = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRetry() {
|
|
||||||
view?.run {
|
|
||||||
showErrorView(false)
|
|
||||||
showProgress(true)
|
|
||||||
}
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDetailsClick() {
|
|
||||||
view?.showErrorDetailsDialog(lastError)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadData(forceRefresh: Boolean = false) {
|
|
||||||
flatResourceFlow {
|
|
||||||
val student = studentRepository.getCurrentStudent()
|
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
|
||||||
getAttendanceCalculatorData(student, semester, forceRefresh)
|
|
||||||
}
|
|
||||||
.logResourceStatus("load attendance calculator")
|
|
||||||
.onResourceData {
|
|
||||||
view?.run {
|
|
||||||
showProgress(false)
|
|
||||||
showErrorView(false)
|
|
||||||
showContent(it.isNotEmpty())
|
|
||||||
showEmpty(it.isEmpty())
|
|
||||||
updateData(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onResourceIntermediate { view?.showRefresh(true) }
|
|
||||||
.onResourceNotLoading {
|
|
||||||
view?.run {
|
|
||||||
enableSwipe(true)
|
|
||||||
showRefresh(false)
|
|
||||||
showProgress(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onResourceError(errorHandler::dispatch)
|
|
||||||
.launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
|
||||||
view?.run {
|
|
||||||
if (isViewEmpty) {
|
|
||||||
lastError = error
|
|
||||||
setErrorDetails(message)
|
|
||||||
showErrorView(true)
|
|
||||||
showEmpty(false)
|
|
||||||
} else showError(message, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingsSelected(): Boolean {
|
|
||||||
view?.openSettingsView()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.pojos.AttendanceData
|
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
|
||||||
|
|
||||||
interface AttendanceCalculatorView : BaseView {
|
|
||||||
|
|
||||||
val isViewEmpty: Boolean
|
|
||||||
|
|
||||||
fun initView()
|
|
||||||
|
|
||||||
fun showRefresh(show: Boolean)
|
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
|
||||||
|
|
||||||
fun showProgress(show: Boolean)
|
|
||||||
|
|
||||||
fun enableSwipe(enable: Boolean)
|
|
||||||
|
|
||||||
fun showEmpty(show: Boolean)
|
|
||||||
|
|
||||||
fun showErrorView(show: Boolean)
|
|
||||||
|
|
||||||
fun setErrorDetails(message: String)
|
|
||||||
|
|
||||||
fun updateData(data: List<AttendanceData>)
|
|
||||||
|
|
||||||
fun clearView()
|
|
||||||
|
|
||||||
fun openSettingsView()
|
|
||||||
}
|
|
|
@ -78,9 +78,4 @@ class AuthDialog : BaseDialogFragment<DialogAuthBinding>(), AuthView {
|
||||||
override fun showDescriptionWithName(name: String) {
|
override fun showDescriptionWithName(name: String) {
|
||||||
binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml()
|
binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
presenter.onDetachView()
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AuthPresenter @Inject constructor(
|
class AuthPresenter @Inject constructor(
|
||||||
|
@ -27,12 +26,8 @@ class AuthPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun loadName() {
|
private fun loadName() {
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching {
|
runCatching { studentRepository.getCurrentStudent(false) }
|
||||||
studentRepository.getCurrentStudent(false)
|
.onSuccess { view?.showDescriptionWithName(it.studentName) }
|
||||||
.studentName
|
|
||||||
.replace(" ", "\u00A0")
|
|
||||||
}
|
|
||||||
.onSuccess { view?.showDescriptionWithName(it) }
|
|
||||||
.onFailure { errorHandler.dispatch(it) }
|
.onFailure { errorHandler.dispatch(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,9 +57,8 @@ class AuthPresenter @Inject constructor(
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
|
||||||
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
|
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
|
||||||
Timber.d("Auth succeed: $isSuccess")
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
studentRepository.refreshStudentAfterAuthorize(student, semester)
|
studentRepository.refreshStudentName(student, semester)
|
||||||
}
|
}
|
||||||
isSuccess
|
isSuccess
|
||||||
}
|
}
|
||||||
|
@ -74,7 +68,6 @@ class AuthPresenter @Inject constructor(
|
||||||
view?.showContent(true)
|
view?.showContent(true)
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
Timber.d("Auth fully succeed: $it")
|
|
||||||
if (it) {
|
if (it) {
|
||||||
view?.showSuccess(true)
|
view?.showSuccess(true)
|
||||||
view?.showContent(false)
|
view?.showContent(false)
|
||||||
|
|
|
@ -10,10 +10,9 @@ import android.webkit.WebViewClient
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.databinding.DialogCaptchaBinding
|
import io.github.wulkanowy.databinding.DialogCaptchaBinding
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -21,10 +20,7 @@ import javax.inject.Inject
|
||||||
class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var wulkanowySdkFactory: WulkanowySdkFactory
|
lateinit var sdk: Sdk
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
|
|
||||||
|
|
||||||
private var webView: WebView? = null
|
private var webView: WebView? = null
|
||||||
|
|
||||||
|
@ -59,7 +55,7 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||||
webView = this
|
webView = this
|
||||||
with(settings) {
|
with(settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = wulkanowySdkFactory.createBase().userAgent
|
userAgentString = sdk.userAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
webViewClient = object : WebViewClient() {
|
webViewClient = object : WebViewClient() {
|
||||||
|
@ -84,7 +80,6 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
webkitCookieManagerProxy.webkitCookieManager?.flush()
|
|
||||||
webView?.destroy()
|
webView?.destroy()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||||
import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
|
@ -126,7 +125,6 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
mainActivity.pushView(ConferenceFragment.newInstance())
|
mainActivity.pushView(ConferenceFragment.newInstance())
|
||||||
}
|
}
|
||||||
onAdminMessageClickListener = presenter::onAdminMessageSelected
|
onAdminMessageClickListener = presenter::onAdminMessageSelected
|
||||||
onPanicButtonClickListener = presenter::onPanicButtonClicked
|
|
||||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
|
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
|
||||||
|
|
||||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
|
@ -210,11 +208,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
binding = binding.dashboardErrorAdminMessage,
|
binding = binding.dashboardErrorAdminMessage,
|
||||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
||||||
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
||||||
onPanicButtonClickListener = presenter::onPanicButtonClicked,
|
).bind(adminMessageItem.adminMessage)
|
||||||
).bind(
|
|
||||||
item = adminMessageItem.adminMessage,
|
|
||||||
showPanicButton = true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,10 +236,6 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
requireContext().openInternetBrowser(url)
|
requireContext().openInternetBrowser(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openPanicWebView(url: String) {
|
|
||||||
(requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
dashboardAdapter.clearTimers()
|
dashboardAdapter.clearTimers()
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import io.github.wulkanowy.data.errorOrNull
|
||||||
import io.github.wulkanowy.data.flatResourceFlow
|
import io.github.wulkanowy.data.flatResourceFlow
|
||||||
import io.github.wulkanowy.data.mapResourceData
|
import io.github.wulkanowy.data.mapResourceData
|
||||||
import io.github.wulkanowy.data.onResourceError
|
import io.github.wulkanowy.data.onResourceError
|
||||||
import io.github.wulkanowy.data.onResourceSuccess
|
|
||||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||||
|
@ -24,7 +23,6 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
|
||||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||||
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
|
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
|
@ -46,7 +44,6 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
@ -285,22 +282,6 @@ class DashboardPresenter @Inject constructor(
|
||||||
url?.let { view?.openInternetBrowser(it) }
|
url?.let { view?.openInternetBrowser(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPanicButtonClicked() {
|
|
||||||
resourceFlow { studentRepository.getCurrentStudent() }
|
|
||||||
.onResourceError { errorHandler.dispatch(it) }
|
|
||||||
.onResourceSuccess {
|
|
||||||
val baseUrl = it.scrapperBaseUrl.toHttpUrl()
|
|
||||||
val urlToOpen = baseUrl.newBuilder()
|
|
||||||
.host("uonetplus${it.scrapperDomainSuffix}.${baseUrl.host}")
|
|
||||||
.addPathSegment(it.symbol)
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
view?.openPanicWebView(urlToOpen)
|
|
||||||
}
|
|
||||||
.launch("panic_button")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||||
flow {
|
flow {
|
||||||
val selectedTiles = selectedDashboardTiles
|
val selectedTiles = selectedDashboardTiles
|
||||||
|
|
|
@ -31,6 +31,4 @@ interface DashboardView : BaseView {
|
||||||
fun openNotificationsCenterView()
|
fun openNotificationsCenterView()
|
||||||
|
|
||||||
fun openInternetBrowser(url: String)
|
fun openInternetBrowser(url: String)
|
||||||
|
|
||||||
fun openPanicWebView(url: String)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,8 +59,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
|
|
||||||
var onAdminMessageClickListener: (String?) -> Unit = {}
|
var onAdminMessageClickListener: (String?) -> Unit = {}
|
||||||
|
|
||||||
var onPanicButtonClickListener: () -> Unit = {}
|
|
||||||
|
|
||||||
var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {}
|
var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {}
|
||||||
|
|
||||||
val items = mutableListOf<DashboardItem>()
|
val items = mutableListOf<DashboardItem>()
|
||||||
|
@ -88,46 +86,35 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder(
|
DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder(
|
||||||
ItemDashboardAccountBinding.inflate(inflater, parent, false)
|
ItemDashboardAccountBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder(
|
DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder(
|
||||||
ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false)
|
ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.GRADES.ordinal -> GradesViewHolder(
|
DashboardItem.Type.GRADES.ordinal -> GradesViewHolder(
|
||||||
ItemDashboardGradesBinding.inflate(inflater, parent, false)
|
ItemDashboardGradesBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder(
|
DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder(
|
||||||
ItemDashboardLessonsBinding.inflate(inflater, parent, false)
|
ItemDashboardLessonsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder(
|
DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder(
|
||||||
ItemDashboardHomeworkBinding.inflate(inflater, parent, false)
|
ItemDashboardHomeworkBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder(
|
DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder(
|
||||||
ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false)
|
ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder(
|
DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder(
|
||||||
ItemDashboardExamsBinding.inflate(inflater, parent, false)
|
ItemDashboardExamsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
||||||
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
|
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
|
||||||
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
|
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
|
||||||
onAdminMessageClickListener = onAdminMessageClickListener,
|
onAdminMessageClickListener = onAdminMessageClickListener,
|
||||||
onPanicButtonClickListener = onPanicButtonClickListener,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
|
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
|
||||||
ItemDashboardAdsBinding.inflate(inflater, parent, false)
|
ItemDashboardAdsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,11 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
||||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||||
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
||||||
is AdminMessageViewHolder -> holder.bind(
|
is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage)
|
||||||
(items[position] as DashboardItem.AdminMessages).adminMessage,
|
|
||||||
showPanicButton = true
|
|
||||||
)
|
|
||||||
|
|
||||||
is AdsViewHolder -> bindAdsViewHolder(holder, position)
|
is AdsViewHolder -> bindAdsViewHolder(holder, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,15 +240,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
attendancePercentage == null || attendancePercentage == .0 -> {
|
attendancePercentage == null || attendancePercentage == .0 -> {
|
||||||
root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||||
root.context.getThemeAttrColor(R.attr.colorPrimary)
|
root.context.getThemeAttrColor(R.attr.colorPrimary)
|
||||||
}
|
}
|
||||||
|
|
||||||
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||||
root.context.getThemeAttrColor(R.attr.colorTimetableChange)
|
root.context.getThemeAttrColor(R.attr.colorTimetableChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
}
|
}
|
||||||
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
|
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
|
||||||
|
@ -356,28 +336,24 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
tomorrowTimetable.isNotEmpty() -> {
|
tomorrowTimetable.isNotEmpty() -> {
|
||||||
dateToNavigate = tomorrowDate
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, tomorrowTimetable, binding)
|
updateLessonView(item, tomorrowTimetable, binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||||
dateToNavigate = currentDate
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
||||||
dateToNavigate = tomorrowDate
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
dateToNavigate = currentDate
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, emptyList(), binding)
|
updateLessonView(item, emptyList(), binding)
|
||||||
|
@ -485,7 +461,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
firstTitleText =
|
firstTitleText =
|
||||||
context.getString(R.string.dashboard_timetable_first_lesson_title_moment)
|
context.getString(R.string.dashboard_timetable_first_lesson_title_moment)
|
||||||
}
|
}
|
||||||
|
|
||||||
minutesToStartLesson < 240 -> {
|
minutesToStartLesson < 240 -> {
|
||||||
firstTitleAndValueTextColor =
|
firstTitleAndValueTextColor =
|
||||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
|
@ -493,7 +468,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
firstTitleText =
|
firstTitleText =
|
||||||
context.getString(R.string.dashboard_timetable_first_lesson_title_soon)
|
context.getString(R.string.dashboard_timetable_first_lesson_title_soon)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
firstTitleAndValueTextColor =
|
firstTitleAndValueTextColor =
|
||||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
|
|
|
@ -13,10 +13,9 @@ class AdminMessageViewHolder(
|
||||||
private val binding: ItemDashboardAdminMessageBinding,
|
private val binding: ItemDashboardAdminMessageBinding,
|
||||||
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
|
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
|
||||||
private val onAdminMessageClickListener: (String?) -> Unit,
|
private val onAdminMessageClickListener: (String?) -> Unit,
|
||||||
private val onPanicButtonClickListener: () -> Unit,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(item: AdminMessage?, showPanicButton: Boolean = false) {
|
fun bind(item: AdminMessage?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
|
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
|
@ -49,14 +48,10 @@ class AdminMessageViewHolder(
|
||||||
dashboardAdminMessageItemClose.setOnClickListener {
|
dashboardAdminMessageItemClose.setOnClickListener {
|
||||||
onAdminMessageDismissClickListener(item)
|
onAdminMessageDismissClickListener(item)
|
||||||
}
|
}
|
||||||
dashboardPanicSection.root.isVisible = showPanicButton
|
|
||||||
dashboardPanicSection.dashboardPanicButton.setOnClickListener {
|
|
||||||
onPanicButtonClickListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardAdminMessage.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||||
item.destinationUrl?.let { url ->
|
item.destinationUrl?.let { url ->
|
||||||
dashboardAdminMessage.setOnClickListener { onAdminMessageClickListener(url) }
|
root.setOnClickListener { onAdminMessageClickListener(url) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,5 @@ private fun generateSummary(subject: String, predicted: String, final: String) =
|
||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = .0,
|
average = .0
|
||||||
pointsSumAllYear = null,
|
|
||||||
averageAllYear = null,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf(
|
||||||
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
studentId = 0,
|
userLoginId = 0,
|
||||||
date = LocalDate.now()
|
date = LocalDate.now()
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.end
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.addCallback
|
|
||||||
import androidx.core.text.HtmlCompat
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.databinding.FragmentEndBinding
|
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class EndFragment : BaseFragment<FragmentEndBinding>(R.layout.fragment_end), EndView {
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding = FragmentEndBinding.bind(view)
|
|
||||||
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback {
|
|
||||||
requireActivity().finishAffinity()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.endClose.setOnClickListener { requireActivity().finishAffinity() }
|
|
||||||
|
|
||||||
val message = getString(R.string.end_message)
|
|
||||||
binding.endDescription.movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
binding.endDescription.text =
|
|
||||||
HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules.end
|
|
||||||
|
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
|
||||||
|
|
||||||
interface EndView : BaseView
|
|
|
@ -159,7 +159,7 @@ class GradeAverageProvider @Inject constructor(
|
||||||
?.updateModifiers(student, config).orEmpty()
|
?.updateModifiers(student, config).orEmpty()
|
||||||
|
|
||||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||||
isOptionalArithmeticAverage = config.isOptionalArithmeticAverage,
|
config.isOptionalArithmeticAverage
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
secondSemesterSubject.average
|
secondSemesterSubject.average
|
||||||
|
@ -173,21 +173,13 @@ class GradeAverageProvider @Inject constructor(
|
||||||
config: AverageCalcParams,
|
config: AverageCalcParams,
|
||||||
): Double {
|
): Double {
|
||||||
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
|
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
|
||||||
val isSecondSemesterHasWeightGrade = secondSemesterSubject.grades
|
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
|
||||||
.any { it.weightValue > .0 }
|
|
||||||
val isSecondSemesterHasArithmeticGrade = secondSemesterSubject.grades
|
|
||||||
.all { it.weightValue == .0 } && config.isOptionalArithmeticAverage
|
|
||||||
val isSecondSemesterHaveAverage =
|
|
||||||
isSecondSemesterHasWeightGrade || isSecondSemesterHasArithmeticGrade
|
|
||||||
|
|
||||||
val divider = if (isSecondSemesterHaveAverage) 2 else 1
|
|
||||||
val secondSemesterAverage = secondSemesterSubject.grades
|
val secondSemesterAverage = secondSemesterSubject.grades
|
||||||
.updateModifiers(student, config)
|
.updateModifiers(student, config)
|
||||||
.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
|
.calcAverage(config.isOptionalArithmeticAverage)
|
||||||
val firstSemesterAverage = firstSemesterSubject?.grades
|
val firstSemesterAverage = firstSemesterSubject?.grades
|
||||||
?.updateModifiers(student, config)
|
?.updateModifiers(student, config)
|
||||||
?.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
|
?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||||
?: secondSemesterAverage
|
|
||||||
|
|
||||||
(secondSemesterAverage + firstSemesterAverage) / divider
|
(secondSemesterAverage + firstSemesterAverage) / divider
|
||||||
} else {
|
} else {
|
||||||
|
@ -233,7 +225,7 @@ class GradeAverageProvider @Inject constructor(
|
||||||
subject = summary.subject,
|
subject = summary.subject,
|
||||||
average = if (!isAnyAverage || params.forceAverageCalc) {
|
average = if (!isAnyAverage || params.forceAverageCalc) {
|
||||||
grades.updateModifiers(student, params)
|
grades.updateModifiers(student, params)
|
||||||
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
.calcAverage(params.isOptionalArithmeticAverage)
|
||||||
} else summary.average,
|
} else summary.average,
|
||||||
points = summary.pointsSum,
|
points = summary.pointsSum,
|
||||||
summary = summary,
|
summary = summary,
|
||||||
|
@ -266,9 +258,7 @@ class GradeAverageProvider @Inject constructor(
|
||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
pointsSumAllYear = null,
|
average = .0
|
||||||
average = .0,
|
|
||||||
averageAllYear = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,15 +286,8 @@ class GradeAverageProvider @Inject constructor(
|
||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
pointsSumAllYear = null,
|
average = if (calcAverage) details.updateModifiers(student, params)
|
||||||
average = when {
|
.calcAverage(params.isOptionalArithmeticAverage) else .0
|
||||||
calcAverage -> details
|
|
||||||
.updateModifiers(student, params)
|
|
||||||
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
|
||||||
|
|
||||||
else -> .0
|
|
||||||
},
|
|
||||||
averageAllYear = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
@ -30,6 +31,14 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradePresenter
|
lateinit var presenter: GradePresenter
|
||||||
|
|
||||||
|
private val pagerAdapter by lazy {
|
||||||
|
BaseFragmentPagerAdapter(
|
||||||
|
fragmentManager = childFragmentManager,
|
||||||
|
pagesCount = 3,
|
||||||
|
lifecycle = lifecycle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private var semesterSwitchMenu: MenuItem? = null
|
private var semesterSwitchMenu: MenuItem? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -43,8 +52,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||||
|
|
||||||
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
||||||
|
|
||||||
private var pagerAdapter: BaseFragmentPagerAdapter? = null
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -64,26 +71,13 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(binding) {
|
|
||||||
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
|
||||||
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initTabs(pageCount: Int) {
|
|
||||||
pagerAdapter = BaseFragmentPagerAdapter(
|
|
||||||
lifecycle = lifecycle,
|
|
||||||
pagesCount = pageCount,
|
|
||||||
fragmentManager = childFragmentManager
|
|
||||||
)
|
|
||||||
|
|
||||||
with(binding.gradeViewPager) {
|
with(binding.gradeViewPager) {
|
||||||
adapter = pagerAdapter
|
adapter = pagerAdapter
|
||||||
offscreenPageLimit = 3
|
offscreenPageLimit = 3
|
||||||
setOnSelectPageListener(presenter::onPageSelected)
|
setOnSelectPageListener(presenter::onPageSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(pagerAdapter!!) {
|
with(pagerAdapter) {
|
||||||
containerId = binding.gradeViewPager.id
|
containerId = binding.gradeViewPager.id
|
||||||
titleFactory = {
|
titleFactory = {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -105,6 +99,11 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
|
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
|
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
@ -170,20 +169,19 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
|
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
|
||||||
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)
|
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)
|
||||||
?.onParentLoadData(semesterId, forceRefresh)
|
?.onParentLoadData(semesterId, forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildParentReselected(index: Int) {
|
override fun notifyChildParentReselected(index: Int) {
|
||||||
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
|
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildSemesterChange(index: Int) {
|
override fun notifyChildSemesterChange(index: Int) {
|
||||||
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
|
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
pagerAdapter = null
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,11 @@ class GradePresenter @Inject constructor(
|
||||||
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
|
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private var selectedIndex = 0
|
private var selectedIndex = 0
|
||||||
|
|
||||||
private var schoolYear = 0
|
private var schoolYear = 0
|
||||||
private var availableSemesters = emptyList<Semester>()
|
|
||||||
|
private var semesters = emptyList<Semester>()
|
||||||
|
|
||||||
private val loadedSemesterId = mutableMapOf<Int, Int>()
|
private val loadedSemesterId = mutableMapOf<Int, Int>()
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
@ -37,7 +40,7 @@ class GradePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCreateMenu() {
|
fun onCreateMenu() {
|
||||||
if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false)
|
if (semesters.isEmpty()) view?.showSemesterSwitch(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
|
@ -46,8 +49,8 @@ class GradePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSemesterSwitch(): Boolean {
|
fun onSemesterSwitch(): Boolean {
|
||||||
if (availableSemesters.isNotEmpty()) {
|
if (semesters.isNotEmpty()) {
|
||||||
view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2))
|
view?.showSemesterDialog(selectedIndex - 1, semesters.take(2))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -80,7 +83,7 @@ class GradePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPageSelected(index: Int) {
|
fun onPageSelected(index: Int) {
|
||||||
if (availableSemesters.isNotEmpty()) loadChild(index)
|
if (semesters.isNotEmpty()) loadChild(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRetry() {
|
fun onRetry() {
|
||||||
|
@ -98,24 +101,16 @@ class GradePresenter @Inject constructor(
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
|
semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
|
||||||
|
|
||||||
student to semesters
|
|
||||||
}
|
}
|
||||||
.logResourceStatus("load grade data")
|
.logResourceStatus("load grade data")
|
||||||
.onResourceData { (student, semesters) ->
|
.onResourceData {
|
||||||
val currentSemester = semesters.getCurrentOrLast()
|
val current = it.getCurrentOrLast()
|
||||||
selectedIndex =
|
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
|
||||||
if (selectedIndex == 0) currentSemester.semesterName else selectedIndex
|
schoolYear = current.schoolYear
|
||||||
schoolYear = currentSemester.schoolYear
|
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
|
||||||
availableSemesters = semesters.filter { semester ->
|
view?.setCurrentSemesterName(current.semesterName, schoolYear)
|
||||||
semester.diaryId == currentSemester.diaryId
|
|
||||||
}
|
|
||||||
|
|
||||||
view?.run {
|
view?.run {
|
||||||
initTabs(if (student.isEduOne == true) 2 else 3)
|
|
||||||
setCurrentSemesterName(currentSemester.semesterName, schoolYear)
|
|
||||||
|
|
||||||
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
|
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
|
||||||
loadChild(currentPageIndex)
|
loadChild(currentPageIndex)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
|
@ -136,10 +131,10 @@ class GradePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
||||||
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}")
|
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}")
|
||||||
|
|
||||||
val newSelectedSemesterId = try {
|
val newSelectedSemesterId = try {
|
||||||
availableSemesters.first { it.semesterName == selectedIndex }.semesterId
|
semesters.first { it.semesterName == selectedIndex }.semesterId
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
Timber.e(e, "Selected semester no exists")
|
Timber.e(e, "Selected semester no exists")
|
||||||
return
|
return
|
||||||
|
|
|
@ -9,8 +9,6 @@ interface GradeView : BaseView {
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun initTabs(pageCount: Int)
|
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
fun showProgress(show: Boolean)
|
fun showProgress(show: Boolean)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue