Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
4d67de8e5f |
@ -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
@ -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
@ -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:
|
||||||
|
13
.gitignore
vendored
@ -71,7 +71,6 @@ captures/
|
|||||||
.idea/deploymentTargetDropDown.xml
|
.idea/deploymentTargetDropDown.xml
|
||||||
.idea/deploymentTargetSelector.xml
|
.idea/deploymentTargetSelector.xml
|
||||||
.idea/kotlinc.xml
|
.idea/kotlinc.xml
|
||||||
.idea/studiobot.xml
|
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
@ -118,14 +117,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 157
|
||||||
versionName "2.7.0"
|
versionName "2.5.8"
|
||||||
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"'
|
||||||
}
|
}
|
||||||
@ -161,7 +165,7 @@ play {
|
|||||||
track = 'production'
|
track = 'production'
|
||||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||||
userFraction = 0.99d
|
userFraction = 0.99d
|
||||||
updatePriority = 2
|
updatePriority = 4
|
||||||
enabled.set(false)
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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.10"
|
||||||
coroutines = "1.8.1"
|
coroutines = "1.8.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'io.github.wulkanowy:sdk:2.7.0'
|
implementation 'io.github.wulkanowy:sdk:2.5.8'
|
||||||
|
|
||||||
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.0"
|
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.3')
|
||||||
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
|
||||||
|
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,8 +13,8 @@ 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
|
||||||
@ -71,7 +71,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,21 +1,13 @@
|
|||||||
package io.github.wulkanowy.data
|
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.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.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.StudentIsEduOne
|
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.Sdk
|
||||||
import io.github.wulkanowy.sdk.scrapper.EvaluateHandler
|
|
||||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||||
import kotlinx.coroutines.guava.await
|
|
||||||
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
|
||||||
@ -24,26 +16,18 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class WulkanowySdkFactory @Inject constructor(
|
class WulkanowySdkFactory @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val chuckerInterceptor: ChuckerInterceptor,
|
private val chuckerInterceptor: ChuckerInterceptor,
|
||||||
private val remoteConfig: RemoteConfigHelper,
|
private val remoteConfig: RemoteConfigHelper,
|
||||||
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val wulkanowyRepository: WulkanowyRepository,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val eduOneMutex = Mutex()
|
private val eduOneMutex = Mutex()
|
||||||
private val migrationFailedStudentIds = mutableSetOf<Long>()
|
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 {
|
private val sdk = Sdk().apply {
|
||||||
androidVersion = Build.VERSION.RELEASE
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
buildTag = Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
setAdditionalCookieManager(webkitCookieManagerProxy)
|
setAdditionalCookieManager(webkitCookieManagerProxy)
|
||||||
@ -52,47 +36,14 @@ class WulkanowySdkFactory @Inject constructor(
|
|||||||
addInterceptor(chuckerInterceptor, network = true)
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createBase() = sdk
|
fun create() = 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 {
|
suspend fun create(student: Student, semester: Semester? = null): Sdk {
|
||||||
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
||||||
return buildSdk(student, semester, overrideIsEduOne)
|
return buildSdk(student, semester, overrideIsEduOne)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildSdk(
|
private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk {
|
||||||
student: Student,
|
|
||||||
semester: Semester?,
|
|
||||||
isStudentEduOne: Boolean
|
|
||||||
): Sdk {
|
|
||||||
return create().apply {
|
return create().apply {
|
||||||
email = student.email
|
email = student.email
|
||||||
password = student.password
|
password = student.password
|
||||||
|
@ -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(),
|
|
||||||
)
|
|
@ -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(),
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
@ -6,8 +6,6 @@ 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.utils.AppWidgetUpdater
|
|
||||||
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
|
||||||
@ -20,7 +18,6 @@ import javax.inject.Singleton
|
|||||||
class LuckyNumberRepository @Inject constructor(
|
class LuckyNumberRepository @Inject constructor(
|
||||||
private val luckyNumberDb: LuckyNumberDao,
|
private val luckyNumberDb: LuckyNumberDao,
|
||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||||
private val appWidgetUpdater: AppWidgetUpdater,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
@ -29,7 +26,6 @@ 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 },
|
||||||
@ -48,9 +44,6 @@ class LuckyNumberRepository @Inject constructor(
|
|||||||
oldItems = listOfNotNull(oldLuckyNumber),
|
oldItems = listOfNotNull(oldLuckyNumber),
|
||||||
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
|
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
|
||||||
)
|
)
|
||||||
if (!isFromAppWidget) {
|
|
||||||
appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -122,7 +122,7 @@ class MessageRepository @Inject constructor(
|
|||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student)
|
wulkanowySdkFactory.create(student)
|
||||||
.getMessageDetails(
|
.getMessageDetails(
|
||||||
messageKey = message.messageGlobalKey,
|
messageKey = it!!.message.messageGlobalKey,
|
||||||
markAsRead = message.unread && markAsRead,
|
markAsRead = message.unread && markAsRead,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -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,7 +1,7 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
import io.github.wulkanowy.data.api.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
|
||||||
|
@ -199,7 +199,7 @@ class StudentRepository @Inject constructor(
|
|||||||
|
|
||||||
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
|
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
|
||||||
val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
|
val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
|
||||||
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
|
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
|
||||||
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
|
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
|
||||||
.getOrNull() ?: return
|
.getOrNull() ?: return
|
||||||
|
|
||||||
|
@ -13,8 +13,6 @@ 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.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.monday
|
import io.github.wulkanowy.utils.monday
|
||||||
@ -28,7 +26,6 @@ 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,
|
||||||
@ -37,7 +34,6 @@ class TimetableRepository @Inject constructor(
|
|||||||
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
private val wulkanowySdkFactory: WulkanowySdkFactory,
|
||||||
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 +52,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 = {
|
||||||
@ -88,9 +83,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(
|
||||||
|
@ -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,7 +15,6 @@ 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.sdk.scrapper.exception.FeatureUnavailableException
|
||||||
@ -43,9 +42,7 @@ 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()
|
||||||
@ -94,7 +91,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()
|
||||||
|
@ -14,7 +14,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
|
||||||
@ -135,7 +134,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,10 +253,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -215,11 +195,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")
|
||||||
|
|
||||||
|
@ -56,8 +56,6 @@ interface AttendanceView : BaseView {
|
|||||||
|
|
||||||
fun openSummaryView()
|
fun openSummaryView()
|
||||||
|
|
||||||
fun openCalculatorView()
|
|
||||||
|
|
||||||
fun startSendMessageIntent(date: LocalDate, numbers: 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()
|
|
||||||
}
|
|
@ -27,12 +27,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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
|||||||
webView = this
|
webView = this
|
||||||
with(settings) {
|
with(settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = wulkanowySdkFactory.createBase().userAgent
|
userAgentString = wulkanowySdkFactory.create().userAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
webViewClient = object : WebViewClient() {
|
webViewClient = object : WebViewClient() {
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -15,7 +15,6 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.databinding.ActivityLoginBinding
|
import io.github.wulkanowy.databinding.ActivityLoginBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.modules.end.EndFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
|
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
|
||||||
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
|
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
|
||||||
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
|
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
|
||||||
@ -116,14 +115,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToEnd() {
|
|
||||||
openFragment(EndFragment(), clearBackStack = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
inAppUpdateHelper.onResume()
|
inAppUpdateHelper.onResume()
|
||||||
presenter.updateSdkMappings()
|
|
||||||
presenter.checkIfEnd()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login
|
package io.github.wulkanowy.ui.modules.login
|
||||||
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
|
||||||
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 timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LoginPresenter @Inject constructor(
|
class LoginPresenter @Inject constructor(
|
||||||
private val wulkanowyRepository: WulkanowyRepository,
|
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository
|
||||||
private val syncManager: SyncManager
|
|
||||||
) : BasePresenter<LoginView>(errorHandler, studentRepository) {
|
) : BasePresenter<LoginView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
override fun onAttachView(view: LoginView) {
|
override fun onAttachView(view: LoginView) {
|
||||||
@ -22,18 +16,4 @@ class LoginPresenter @Inject constructor(
|
|||||||
view.initView()
|
view.initView()
|
||||||
Timber.i("Login view was initialized")
|
Timber.i("Login view was initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSdkMappings() {
|
|
||||||
presenterScope.launch {
|
|
||||||
runCatching { wulkanowyRepository.fetchMapping() }
|
|
||||||
.onFailure { Timber.e(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkIfEnd() {
|
|
||||||
if (isEndDateReached) {
|
|
||||||
syncManager.stopSyncWorker()
|
|
||||||
view?.navigateToEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,4 @@ import io.github.wulkanowy.ui.base.BaseView
|
|||||||
interface LoginView : BaseView {
|
interface LoginView : BaseView {
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun navigateToEnd()
|
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,6 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
binding = binding.loginFormMessage,
|
binding = binding.loginFormMessage,
|
||||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
||||||
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
||||||
onPanicButtonClickListener = {},
|
|
||||||
).bind(message)
|
).bind(message)
|
||||||
binding.loginFormMessage.root.isVisible = message != null
|
binding.loginFormMessage.root.isVisible = message != null
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,10 @@ import androidx.core.os.bundleOf
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||||
@ -113,20 +111,6 @@ class LoginStudentSelectFragment :
|
|||||||
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAdminMessage(adminMessage: AdminMessage?) {
|
|
||||||
AdminMessageViewHolder(
|
|
||||||
binding = binding.loginStudentSelectAdminMessage,
|
|
||||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
|
||||||
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
|
||||||
onPanicButtonClickListener = {},
|
|
||||||
).bind(adminMessage)
|
|
||||||
binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openInternetBrowser(url: String) {
|
|
||||||
requireContext().openInternetBrowser(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -2,23 +2,16 @@ package io.github.wulkanowy.ui.modules.login.studentselect
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
import io.github.wulkanowy.data.dataOrNull
|
import io.github.wulkanowy.data.dataOrNull
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.enums.MessageType
|
|
||||||
import io.github.wulkanowy.data.flatResourceFlow
|
|
||||||
import io.github.wulkanowy.data.logResourceStatus
|
import io.github.wulkanowy.data.logResourceStatus
|
||||||
import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
|
import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
|
||||||
import io.github.wulkanowy.data.onResourceData
|
|
||||||
import io.github.wulkanowy.data.onResourceError
|
|
||||||
import io.github.wulkanowy.data.pojos.RegisterStudent
|
import io.github.wulkanowy.data.pojos.RegisterStudent
|
||||||
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUnit
|
import io.github.wulkanowy.data.pojos.RegisterUnit
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.SchoolsRepository
|
import io.github.wulkanowy.data.repositories.SchoolsRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException
|
import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
@ -40,8 +33,6 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
private val syncManager: SyncManager,
|
private val syncManager: SyncManager,
|
||||||
private val analytics: AnalyticsHelper,
|
private val analytics: AnalyticsHelper,
|
||||||
private val appInfo: AppInfo,
|
private val appInfo: AppInfo,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
|
||||||
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase
|
|
||||||
) : BasePresenter<LoginStudentSelectView>(loginErrorHandler, studentRepository) {
|
) : BasePresenter<LoginStudentSelectView>(loginErrorHandler, studentRepository) {
|
||||||
|
|
||||||
private var lastError: Throwable? = null
|
private var lastError: Throwable? = null
|
||||||
@ -74,7 +65,6 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
this.loginData = loginData
|
this.loginData = loginData
|
||||||
this.registerUser = registerUser
|
this.registerUser = registerUser
|
||||||
loadData()
|
loadData()
|
||||||
loadAdminMessage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
@ -98,20 +88,7 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
refreshItems()
|
refreshItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launch("load_data")
|
}.launch()
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadAdminMessage() {
|
|
||||||
flatResourceFlow {
|
|
||||||
getAppropriateAdminMessageUseCase(
|
|
||||||
scrapperBaseUrl = registerUser.scrapperBaseUrl.orEmpty(),
|
|
||||||
type = MessageType.LOGIN_STUDENT_SELECT_MESSAGE,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.logResourceStatus("load login admin message")
|
|
||||||
.onResourceData { view?.showAdminMessage(it) }
|
|
||||||
.onResourceError { view?.showAdminMessage(null) }
|
|
||||||
.launch("load_admin_message")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStudentsWithCurrentlyActiveSemesters(): List<LoginStudentSelectItem.Student> {
|
private fun getStudentsWithCurrentlyActiveSemesters(): List<LoginStudentSelectItem.Student> {
|
||||||
@ -364,14 +341,4 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAdminMessageSelected(url: String?) {
|
|
||||||
url?.let { view?.openInternetBrowser(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
|
|
||||||
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
|
|
||||||
|
|
||||||
view?.showAdminMessage(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login.studentselect
|
package io.github.wulkanowy.ui.modules.login.studentselect
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||||
@ -26,8 +25,4 @@ interface LoginStudentSelectView : BaseView {
|
|||||||
fun openDiscordInvite()
|
fun openDiscordInvite()
|
||||||
|
|
||||||
fun openEmail(supportInfo: LoginSupportInfo)
|
fun openEmail(supportInfo: LoginSupportInfo)
|
||||||
|
|
||||||
fun showAdminMessage(adminMessage: AdminMessage?)
|
|
||||||
|
|
||||||
fun openInternetBrowser(url: String)
|
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,13 @@ import android.view.inputmethod.EditorInfo.IME_NULL
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
|
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||||
@ -182,18 +179,4 @@ class LoginSymbolFragment :
|
|||||||
override fun openSupportDialog(supportInfo: LoginSupportInfo) {
|
override fun openSupportDialog(supportInfo: LoginSupportInfo) {
|
||||||
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAdminMessage(adminMessage: AdminMessage?) {
|
|
||||||
AdminMessageViewHolder(
|
|
||||||
binding = binding.loginSymbolAdminMessage,
|
|
||||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
|
||||||
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
|
||||||
onPanicButtonClickListener = {},
|
|
||||||
).bind(adminMessage)
|
|
||||||
binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openInternetBrowser(url: String) {
|
|
||||||
requireContext().openInternetBrowser(url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,10 @@ package io.github.wulkanowy.ui.modules.login.symbol
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
import io.github.wulkanowy.data.dataOrNull
|
import io.github.wulkanowy.data.dataOrNull
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.enums.MessageType
|
|
||||||
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.onResourceNotLoading
|
import io.github.wulkanowy.data.onResourceNotLoading
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
|
||||||
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
|
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -29,9 +21,7 @@ import javax.inject.Inject
|
|||||||
class LoginSymbolPresenter @Inject constructor(
|
class LoginSymbolPresenter @Inject constructor(
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val loginErrorHandler: LoginErrorHandler,
|
private val loginErrorHandler: LoginErrorHandler,
|
||||||
private val analytics: AnalyticsHelper,
|
private val analytics: AnalyticsHelper
|
||||||
private val preferencesRepository: PreferencesRepository,
|
|
||||||
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
|
|
||||||
) : BasePresenter<LoginSymbolView>(loginErrorHandler, studentRepository) {
|
) : BasePresenter<LoginSymbolView>(loginErrorHandler, studentRepository) {
|
||||||
|
|
||||||
private var lastError: Throwable? = null
|
private var lastError: Throwable? = null
|
||||||
@ -53,21 +43,6 @@ class LoginSymbolPresenter @Inject constructor(
|
|||||||
clearAndFocusSymbol()
|
clearAndFocusSymbol()
|
||||||
showSoftKeyboard()
|
showSoftKeyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAdminMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadAdminMessage() {
|
|
||||||
flatResourceFlow {
|
|
||||||
getAppropriateAdminMessageUseCase(
|
|
||||||
scrapperBaseUrl = loginData.baseUrl,
|
|
||||||
type = MessageType.LOGIN_SYMBOL_MESSAGE,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.logResourceStatus("load login admin message")
|
|
||||||
.onResourceData { view?.showAdminMessage(it) }
|
|
||||||
.onResourceError { view?.showAdminMessage(null) }
|
|
||||||
.launch("load_admin_message")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSymbolTextChanged() {
|
fun onSymbolTextChanged() {
|
||||||
@ -191,14 +166,4 @@ class LoginSymbolPresenter @Inject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAdminMessageSelected(url: String?) {
|
|
||||||
url?.let { view?.openInternetBrowser(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
|
|
||||||
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
|
|
||||||
|
|
||||||
view?.showAdminMessage(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login.symbol
|
package io.github.wulkanowy.ui.modules.login.symbol
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||||
@ -45,8 +44,4 @@ interface LoginSymbolView : BaseView {
|
|||||||
fun openFaqPage()
|
fun openFaqPage()
|
||||||
|
|
||||||
fun openSupportDialog(supportInfo: LoginSupportInfo)
|
fun openSupportDialog(supportInfo: LoginSupportInfo)
|
||||||
|
|
||||||
fun showAdminMessage(adminMessage: AdminMessage?)
|
|
||||||
|
|
||||||
fun openInternetBrowser(url: String)
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,7 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.dataOrThrow
|
import io.github.wulkanowy.data.dataOrThrow
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.data.toFirstResult
|
import io.github.wulkanowy.data.toFirstResult
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
@ -37,9 +35,6 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var sharedPref: SharedPrefProvider
|
lateinit var sharedPref: SharedPrefProvider
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var preferencesRepository: PreferencesRepository
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196
|
private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196
|
||||||
|
|
||||||
@ -135,10 +130,6 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
|
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
|
||||||
if (isEndDateReached) {
|
|
||||||
return@runBlocking null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val students = studentRepository.getSavedStudents()
|
val students = studentRepository.getSavedStudents()
|
||||||
val student = students.singleOrNull { it.student.id == studentId }?.student
|
val student = students.singleOrNull { it.student.id == studentId }?.student
|
||||||
@ -154,11 +145,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentStudent != null) {
|
if (currentStudent != null) {
|
||||||
luckyNumberRepository.getLuckyNumber(
|
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
|
||||||
student = currentStudent,
|
|
||||||
forceRefresh = false,
|
|
||||||
isFromAppWidget = true
|
|
||||||
)
|
|
||||||
.toFirstResult()
|
.toFirstResult()
|
||||||
.dataOrThrow
|
.dataOrThrow
|
||||||
} else null
|
} else null
|
||||||
|
@ -33,7 +33,6 @@ import io.github.wulkanowy.ui.modules.Destination
|
|||||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
||||||
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
||||||
import io.github.wulkanowy.ui.modules.end.EndFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
|
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
@ -139,8 +138,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
inAppUpdateHelper.onResume()
|
inAppUpdateHelper.onResume()
|
||||||
presenter.updateSdkMappings()
|
|
||||||
presenter.checkIfEnd()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
@ -364,10 +361,4 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navController.onSaveInstanceState(outState)
|
navController.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToEnd() {
|
|
||||||
binding.mainToolbar.isVisible = false
|
|
||||||
pushView(EndFragment())
|
|
||||||
onBackCallback?.isEnabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import io.github.wulkanowy.data.onResourceError
|
|||||||
import io.github.wulkanowy.data.onResourceSuccess
|
import io.github.wulkanowy.data.onResourceSuccess
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -16,7 +14,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler
|
|||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.account.AccountView
|
import io.github.wulkanowy.ui.modules.account.AccountView
|
||||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView
|
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView
|
||||||
import io.github.wulkanowy.ui.modules.end.EndView
|
|
||||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||||
import io.github.wulkanowy.utils.AdsHelper
|
import io.github.wulkanowy.utils.AdsHelper
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
@ -32,7 +29,6 @@ class MainPresenter @Inject constructor(
|
|||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val wulkanowyRepository: WulkanowyRepository,
|
|
||||||
private val syncManager: SyncManager,
|
private val syncManager: SyncManager,
|
||||||
private val analytics: AnalyticsHelper,
|
private val analytics: AnalyticsHelper,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
@ -112,7 +108,6 @@ class MainPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) {
|
private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) {
|
||||||
is EndView,
|
|
||||||
is AccountView,
|
is AccountView,
|
||||||
is StudentInfoView,
|
is StudentInfoView,
|
||||||
is AccountDetailsView -> false
|
is AccountDetailsView -> false
|
||||||
@ -204,18 +199,4 @@ class MainPresenter @Inject constructor(
|
|||||||
.onFailure { errorHandler.dispatch(it) }
|
.onFailure { errorHandler.dispatch(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSdkMappings() {
|
|
||||||
presenterScope.launch {
|
|
||||||
runCatching { wulkanowyRepository.fetchMapping() }
|
|
||||||
.onFailure { Timber.e(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkIfEnd() {
|
|
||||||
if (isEndDateReached) {
|
|
||||||
syncManager.stopSyncWorker()
|
|
||||||
view?.navigateToEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,6 @@ interface MainView : BaseView {
|
|||||||
|
|
||||||
fun openMoreDestination(destination: Destination)
|
fun openMoreDestination(destination: Destination)
|
||||||
|
|
||||||
fun navigateToEnd()
|
|
||||||
|
|
||||||
interface MainChildView {
|
interface MainChildView {
|
||||||
|
|
||||||
fun onFragmentReselected()
|
fun onFragmentReselected()
|
||||||
|
@ -27,7 +27,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
|||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
|
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
|
||||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||||
import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
|
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
@ -133,7 +132,6 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
|||||||
)
|
)
|
||||||
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
|
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
messageTabPanicSection.dashboardPanicButton.setOnClickListener { presenter.onPanicButtonClicked() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
|
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
|
||||||
@ -285,10 +283,6 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openPanicWebView(url: String) {
|
|
||||||
(requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hideKeyboard() {
|
override fun hideKeyboard() {
|
||||||
activity?.hideSoftInput()
|
activity?.hideSoftInput()
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.debounce
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
@ -430,20 +429,4 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
+ dateRatio.toDouble().pow(2) * 2
|
+ dateRatio.toDouble().pow(2) * 2
|
||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPanicButtonClicked() {
|
|
||||||
resourceFlow { studentRepository.getCurrentStudent() }
|
|
||||||
.onResourceError { errorHandler.dispatch(it) }
|
|
||||||
.onResourceSuccess {
|
|
||||||
val baseUrl = it.scrapperBaseUrl.toHttpUrl()
|
|
||||||
val urlToOpen = baseUrl.newBuilder()
|
|
||||||
.host("uonetplus${it.scrapperDomainSuffix}-wiadomosciplus.${baseUrl.host}")
|
|
||||||
.addPathSegment(it.symbol)
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
view?.openPanicWebView(urlToOpen)
|
|
||||||
}
|
|
||||||
.launch("panic_button")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,4 @@ interface MessageTabView : BaseView {
|
|||||||
fun showRecyclerBottomPadding(show: Boolean)
|
fun showRecyclerBottomPadding(show: Boolean)
|
||||||
|
|
||||||
fun showMailboxChooser(mailboxes: List<Mailbox>)
|
fun showMailboxChooser(mailboxes: List<Mailbox>)
|
||||||
|
|
||||||
fun openPanicWebView(url: String)
|
|
||||||
}
|
}
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.panicmode
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import androidx.activity.addCallback
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
|
||||||
import io.github.wulkanowy.databinding.FragmentPanicModeBinding
|
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
|
||||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class PanicModeFragment : BaseFragment<FragmentPanicModeBinding>(R.layout.fragment_panic_mode),
|
|
||||||
MainView.TitledView {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var wulkanowySdkFactory: WulkanowySdkFactory
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
|
|
||||||
|
|
||||||
private var webView: WebView? = null
|
|
||||||
|
|
||||||
override val titleStringId: Int get() = R.string.panic_mode_title
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val PANIC_URL = "panic_mode_url"
|
|
||||||
fun newInstance(url: String?): PanicModeFragment {
|
|
||||||
return PanicModeFragment().apply {
|
|
||||||
arguments = bundleOf(PANIC_URL to url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding = FragmentPanicModeBinding.bind(view)
|
|
||||||
|
|
||||||
binding.panicModeRefresh.setOnClickListener {
|
|
||||||
binding.panicModeWebview.loadUrl(
|
|
||||||
binding.panicModeWebview.url ?: arguments?.getString(PANIC_URL).orEmpty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
binding.panicModeBack.setOnClickListener { binding.panicModeWebview.goBack() }
|
|
||||||
binding.panicModeHome.setOnClickListener {
|
|
||||||
binding.panicModeWebview.loadUrl(
|
|
||||||
arguments?.getString(PANIC_URL).orEmpty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
binding.panicModeForward.setOnClickListener { binding.panicModeWebview.goForward() }
|
|
||||||
binding.panicModeShare.setOnClickListener {
|
|
||||||
requireContext().openInternetBrowser(
|
|
||||||
binding.panicModeWebview.url.toString(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val onBackPressedCallback = requireActivity().onBackPressedDispatcher
|
|
||||||
.addCallback(viewLifecycleOwner) {
|
|
||||||
binding.panicModeWebview.goBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
with(binding.panicModeWebview) {
|
|
||||||
webView = this
|
|
||||||
with(settings) {
|
|
||||||
javaScriptEnabled = true
|
|
||||||
userAgentString = wulkanowySdkFactory.createBase().userAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
webViewClient = object : WebViewClient() {
|
|
||||||
override fun doUpdateVisitedHistory(
|
|
||||||
view: WebView?,
|
|
||||||
url: String?,
|
|
||||||
isReload: Boolean
|
|
||||||
) {
|
|
||||||
binding.panicModeBack.isEnabled = binding.panicModeWebview.canGoBack()
|
|
||||||
binding.panicModeForward.isEnabled = binding.panicModeWebview.canGoForward()
|
|
||||||
onBackPressedCallback.isEnabled = binding.panicModeWebview.canGoBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadUrl(arguments?.getString(PANIC_URL).orEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
webkitCookieManagerProxy.webkitCookieManager?.flush()
|
|
||||||
webView?.destroy()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,9 +3,7 @@ package io.github.wulkanowy.ui.modules.settings.appearance
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SeekBarPreference
|
|
||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
@ -31,31 +29,13 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.pref_settings_appearance_title
|
override val titleStringId get() = R.string.pref_settings_appearance_title
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun withFocusedPreference(key: String) = AppearanceFragment().apply {
|
|
||||||
arguments = bundleOf(FOCUSED_KEY to key)
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val FOCUSED_KEY = "focusedKey"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
arguments?.getString(FOCUSED_KEY)?.let { scrollToPreference(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey)
|
setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey)
|
||||||
val attendanceTargetPref =
|
|
||||||
findPreference<SeekBarPreference>(requireContext().getString(R.string.pref_key_attendance_target))!!
|
|
||||||
attendanceTargetPref.setOnPreferenceChangeListener { _, newValueObj ->
|
|
||||||
val newValue = (((newValueObj as Int).toDouble() + 2.5) / 5).toInt() * 5
|
|
||||||
attendanceTargetPref.value =
|
|
||||||
newValue.coerceIn(attendanceTargetPref.min, attendanceTargetPref.max)
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
@ -7,21 +7,20 @@ import android.view.ViewGroup
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableBinding
|
import io.github.wulkanowy.databinding.ItemTimetableBinding
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
|
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableMainAdditionalBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
|
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
|
||||||
import io.github.wulkanowy.utils.SyncListAdapter
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimetableAdapter @Inject constructor() :
|
class TimetableAdapter @Inject constructor() :
|
||||||
SyncListAdapter<TimetableItem, RecyclerView.ViewHolder>(Differ) {
|
ListAdapter<TimetableItem, RecyclerView.ViewHolder>(differ) {
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
|
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
|
||||||
|
|
||||||
@ -40,10 +39,6 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
TimetableItemType.EMPTY -> EmptyViewHolder(
|
TimetableItemType.EMPTY -> EmptyViewHolder(
|
||||||
ItemTimetableEmptyBinding.inflate(inflater, parent, false)
|
ItemTimetableEmptyBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
TimetableItemType.ADDITIONAL -> AdditionalViewHolder(
|
|
||||||
ItemTimetableMainAdditionalBinding.inflate(inflater, parent, false)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,30 +61,16 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Small,
|
item = getItem(position) as TimetableItem.Small,
|
||||||
)
|
)
|
||||||
|
|
||||||
is NormalViewHolder -> bindNormalView(
|
is NormalViewHolder -> bindNormalView(
|
||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Normal,
|
item = getItem(position) as TimetableItem.Normal,
|
||||||
)
|
)
|
||||||
|
|
||||||
is EmptyViewHolder -> bindEmptyView(
|
is EmptyViewHolder -> bindEmptyView(
|
||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Empty,
|
item = getItem(position) as TimetableItem.Empty,
|
||||||
)
|
)
|
||||||
|
|
||||||
is AdditionalViewHolder -> bindAdditionalView(
|
|
||||||
binding = holder.binding,
|
|
||||||
item = getItem(position) as TimetableItem.Additional,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindAdditionalView(
|
|
||||||
binding: ItemTimetableMainAdditionalBinding,
|
|
||||||
item: TimetableItem.Additional
|
|
||||||
) {
|
|
||||||
with(binding) {
|
|
||||||
timetableItemSubject.text = item.additional.subject
|
|
||||||
timetableItemTimeStart.text = item.additional.start.toFormattedString("HH:mm")
|
|
||||||
timetableItemTimeFinish.text = item.additional.end.toFormattedString("HH:mm")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,32 +307,31 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
|
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
private class AdditionalViewHolder(val binding: ItemTimetableMainAdditionalBinding) :
|
companion object {
|
||||||
RecyclerView.ViewHolder(binding.root)
|
private val differ = object : DiffUtil.ItemCallback<TimetableItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
|
||||||
|
when {
|
||||||
|
oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
|
||||||
|
oldItem.lesson.start == newItem.lesson.start
|
||||||
|
}
|
||||||
|
|
||||||
private object Differ : DiffUtil.ItemCallback<TimetableItem>() {
|
oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> {
|
||||||
override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
|
oldItem.lesson.start == newItem.lesson.start
|
||||||
when {
|
}
|
||||||
oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
|
|
||||||
oldItem.lesson.start == newItem.lesson.start
|
else -> oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> {
|
override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) =
|
||||||
oldItem.lesson.start == newItem.lesson.start
|
oldItem == newItem
|
||||||
}
|
|
||||||
|
|
||||||
else -> oldItem == newItem
|
override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? {
|
||||||
}
|
return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) {
|
||||||
|
if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) {
|
||||||
override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) =
|
"time_left"
|
||||||
oldItem == newItem
|
} else super.getChangePayload(oldItem, newItem)
|
||||||
|
|
||||||
override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? {
|
|
||||||
return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) {
|
|
||||||
if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) {
|
|
||||||
"time_left"
|
|
||||||
} else super.getChangePayload(oldItem, newItem)
|
} else super.getChangePayload(oldItem, newItem)
|
||||||
} else super.getChangePayload(oldItem, newItem)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
|||||||
import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment
|
import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
|
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
|
||||||
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
|
|
||||||
import io.github.wulkanowy.utils.openMaterialDatePicker
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -108,11 +104,8 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<TimetableItem>, isDayChanged: Boolean) {
|
override fun updateData(data: List<TimetableItem>) {
|
||||||
when {
|
timetableAdapter.submitList(data)
|
||||||
isDayChanged -> timetableAdapter.recreate(data)
|
|
||||||
else -> timetableAdapter.submitList(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetable
|
package io.github.wulkanowy.ui.modules.timetable
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
sealed class TimetableItem(val type: TimetableItemType) {
|
sealed class TimetableItem(val type: TimetableItemType) {
|
||||||
@ -24,10 +23,6 @@ sealed class TimetableItem(val type: TimetableItemType) {
|
|||||||
val numFrom: Int,
|
val numFrom: Int,
|
||||||
val numTo: Int
|
val numTo: Int
|
||||||
) : TimetableItem(TimetableItemType.EMPTY)
|
) : TimetableItem(TimetableItemType.EMPTY)
|
||||||
|
|
||||||
data class Additional(
|
|
||||||
val additional: TimetableAdditional,
|
|
||||||
) : TimetableItem(TimetableItemType.ADDITIONAL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TimeLeft(
|
data class TimeLeft(
|
||||||
@ -39,6 +34,5 @@ data class TimeLeft(
|
|||||||
enum class TimetableItemType {
|
enum class TimetableItemType {
|
||||||
SMALL,
|
SMALL,
|
||||||
NORMAL,
|
NORMAL,
|
||||||
EMPTY,
|
EMPTY
|
||||||
ADDITIONAL,
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,6 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
|
||||||
import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.BELOW
|
|
||||||
import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.NONE
|
|
||||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
||||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
||||||
import io.github.wulkanowy.data.enums.TimetableMode
|
import io.github.wulkanowy.data.enums.TimetableMode
|
||||||
@ -17,7 +14,6 @@ import io.github.wulkanowy.data.onResourceError
|
|||||||
import io.github.wulkanowy.data.onResourceIntermediate
|
import io.github.wulkanowy.data.onResourceIntermediate
|
||||||
import io.github.wulkanowy.data.onResourceNotLoading
|
import io.github.wulkanowy.data.onResourceNotLoading
|
||||||
import io.github.wulkanowy.data.onResourceSuccess
|
import io.github.wulkanowy.data.onResourceSuccess
|
||||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
|
||||||
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
|
||||||
@ -85,7 +81,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
} else currentDate?.previousSchoolDay
|
} else currentDate?.previousSchoolDay
|
||||||
|
|
||||||
reloadView(date ?: return)
|
reloadView(date ?: return)
|
||||||
loadData(isDayChanged = true)
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextDay() {
|
fun onNextDay() {
|
||||||
@ -94,7 +90,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
} else currentDate?.nextSchoolDay
|
} else currentDate?.nextSchoolDay
|
||||||
|
|
||||||
reloadView(date ?: return)
|
reloadView(date ?: return)
|
||||||
loadData(isDayChanged = true)
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPickDate() {
|
fun onPickDate() {
|
||||||
@ -108,7 +104,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
|
|
||||||
fun onSwipeRefresh() {
|
fun onSwipeRefresh() {
|
||||||
Timber.i("Force refreshing the timetable")
|
Timber.i("Force refreshing the timetable")
|
||||||
loadData(forceRefresh = true)
|
loadData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRetry() {
|
fun onRetry() {
|
||||||
@ -116,7 +112,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
}
|
}
|
||||||
loadData(forceRefresh = true)
|
loadData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDetailsClick() {
|
fun onDetailsClick() {
|
||||||
@ -149,7 +145,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData(forceRefresh: Boolean = false, isDayChanged: Boolean = false) {
|
private fun loadData(forceRefresh: Boolean = false) {
|
||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
@ -173,9 +169,9 @@ class TimetablePresenter @Inject constructor(
|
|||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
updateData(it, isDayChanged)
|
showContent(it.lessons.isNotEmpty())
|
||||||
showContent(it.lessons.isNotEmpty() || it.additional.isNotEmpty())
|
showEmpty(it.lessons.isEmpty())
|
||||||
showEmpty(it.lessons.isEmpty() && it.additional.isEmpty())
|
updateData(it.lessons)
|
||||||
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
|
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
|
||||||
reloadNavigation()
|
reloadNavigation()
|
||||||
}
|
}
|
||||||
@ -220,97 +216,67 @@ class TimetablePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateData(lessons: TimetableFull, isDayChanged: Boolean) {
|
private fun updateData(lessons: List<Timetable>) {
|
||||||
tickTimer?.cancel()
|
tickTimer?.cancel()
|
||||||
|
|
||||||
view?.updateData(createItems(lessons), isDayChanged)
|
if (currentDate != now()) {
|
||||||
if (currentDate == now()) {
|
view?.updateData(createItems(lessons))
|
||||||
tickTimer = timer(period = 2_000, initialDelay = 2_000) {
|
} else {
|
||||||
|
tickTimer = timer(period = 2_000) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
view?.updateData(createItems(lessons), isDayChanged)
|
view?.updateData(createItems(lessons))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Item(
|
private fun createItems(items: List<Timetable>): List<TimetableItem> {
|
||||||
val isStudentPlan: Boolean,
|
val filteredItems = items
|
||||||
val start: Instant,
|
.filter {
|
||||||
val number: Int?,
|
if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
|
||||||
) {
|
it.isStudentPlan
|
||||||
class Lesson(val lesson: Timetable) :
|
} else true
|
||||||
Item(lesson.isStudentPlan, lesson.start, lesson.number)
|
}
|
||||||
|
.sortedWith(compareBy({ item -> item.start }, { item -> !item.isStudentPlan }))
|
||||||
class Additional(val additional: TimetableAdditional) : Item(true, additional.start, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createItems(fullTimetable: TimetableFull): List<TimetableItem> {
|
|
||||||
val showAdditionalLessonsInPlan = prefRepository.showAdditionalLessonsInPlan
|
|
||||||
val allItems =
|
|
||||||
fullTimetable.lessons.map(Item::Lesson) + fullTimetable.additional.map(Item::Additional)
|
|
||||||
.takeIf { showAdditionalLessonsInPlan != NONE }.orEmpty()
|
|
||||||
|
|
||||||
val filteredItems = allItems.filter {
|
|
||||||
if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
|
|
||||||
it.isStudentPlan
|
|
||||||
} else true
|
|
||||||
}.sortedWith(
|
|
||||||
(compareBy<Item> { it is Item.Additional }
|
|
||||||
.takeIf { showAdditionalLessonsInPlan == BELOW } ?: EmptyComparator())
|
|
||||||
.thenBy { it.start }
|
|
||||||
.thenBy { !it.isStudentPlan }
|
|
||||||
)
|
|
||||||
|
|
||||||
var prevNum = when (prefRepository.showTimetableGaps) {
|
var prevNum = when (prefRepository.showTimetableGaps) {
|
||||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
var prevIsAdditional = false
|
|
||||||
return buildList {
|
return buildList {
|
||||||
filteredItems.forEachIndexed { i, it ->
|
filteredItems.forEachIndexed { i, it ->
|
||||||
if (prefRepository.showTimetableGaps != NO_GAPS) {
|
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
|
||||||
if (prevNum != null && it.number != null && it.number > prevNum!! + 1) {
|
val emptyLesson = TimetableItem.Empty(
|
||||||
if (!prevIsAdditional) {
|
numFrom = prevNum!! + 1,
|
||||||
// Additional lessons do count as a lesson so don't add empty lessons
|
numTo = it.number - 1
|
||||||
// when there is an additional lesson present
|
)
|
||||||
val emptyLesson = TimetableItem.Empty(
|
add(emptyLesson)
|
||||||
numFrom = prevNum!! + 1, numTo = it.number - 1
|
|
||||||
)
|
|
||||||
add(emptyLesson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevNum = it.number
|
|
||||||
prevIsAdditional = it is Item.Additional
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it is Item.Lesson) {
|
if (it.isStudentPlan) {
|
||||||
if (it.isStudentPlan) {
|
val normalLesson = TimetableItem.Normal(
|
||||||
val normalLesson = TimetableItem.Normal(
|
lesson = it,
|
||||||
lesson = it.lesson,
|
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
||||||
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
timeLeft = filteredItems.getTimeLeftForLesson(it, i),
|
||||||
timeLeft = filteredItems.getTimeLeftForLesson(it.lesson, i),
|
onClick = ::onTimetableItemSelected,
|
||||||
onClick = ::onTimetableItemSelected,
|
isLessonNumberVisible = !isEduOne
|
||||||
isLessonNumberVisible = !isEduOne
|
)
|
||||||
)
|
add(normalLesson)
|
||||||
add(normalLesson)
|
} else {
|
||||||
} else {
|
val smallLesson = TimetableItem.Small(
|
||||||
val smallLesson = TimetableItem.Small(
|
lesson = it,
|
||||||
lesson = it.lesson,
|
onClick = ::onTimetableItemSelected,
|
||||||
onClick = ::onTimetableItemSelected,
|
isLessonNumberVisible = !isEduOne
|
||||||
isLessonNumberVisible = !isEduOne
|
)
|
||||||
)
|
add(smallLesson)
|
||||||
add(smallLesson)
|
|
||||||
}
|
|
||||||
} else if (it is Item.Additional) {
|
|
||||||
// If the user disabled showing additional lessons, they would've been filtered
|
|
||||||
// out already, so there's no need to check it again.
|
|
||||||
add(TimetableItem.Additional(it.additional))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevNum = it.number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Item>.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft {
|
private fun List<Timetable>.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft {
|
||||||
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index))
|
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index))
|
||||||
return TimeLeft(
|
return TimeLeft(
|
||||||
until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil },
|
until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil },
|
||||||
@ -319,20 +285,11 @@ class TimetablePresenter @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Item>.getPreviousLesson(position: Int): Instant? {
|
private fun List<Timetable>.getPreviousLesson(position: Int): Instant? {
|
||||||
val lessonAdditionalOffset = filterIndexed { i, item ->
|
return filter { it.isStudentPlan }
|
||||||
i < position && item is Item.Additional
|
.getOrNull(position - 1 - filterIndexed { i, item -> i < position && !item.isStudentPlan }.size)
|
||||||
}.size
|
|
||||||
val lessonStudentPlanOffset = filterIndexed { i, item ->
|
|
||||||
i < position && !item.isStudentPlan
|
|
||||||
}.size
|
|
||||||
val lessonIndex = position - 1 - lessonAdditionalOffset - lessonStudentPlanOffset
|
|
||||||
|
|
||||||
return filterIsInstance<Item.Lesson>()
|
|
||||||
.filter { it.isStudentPlan }
|
|
||||||
.getOrNull(lessonIndex)
|
|
||||||
?.let {
|
?.let {
|
||||||
if (!it.lesson.canceled && it.isStudentPlan) it.lesson.end
|
if (!it.canceled && it.isStudentPlan) it.end
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,7 +342,3 @@ class TimetablePresenter @Inject constructor(
|
|||||||
super.onDetachView()
|
super.onDetachView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EmptyComparator<T> : Comparator<T> {
|
|
||||||
override fun compare(o1: T, o2: T) = 0
|
|
||||||
}
|
|
||||||
|
@ -12,7 +12,7 @@ interface TimetableView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<TimetableItem>, isDayChanged: Boolean)
|
fun updateData(data: List<TimetableItem>)
|
||||||
|
|
||||||
fun updateNavigationDay(date: String)
|
fun updateNavigationDay(date: String)
|
||||||
|
|
||||||
|
@ -13,11 +13,7 @@ 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.timetable.additional.add.AdditionalLessonAddDialog
|
import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
|
||||||
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
|
|
||||||
import io.github.wulkanowy.utils.openMaterialDatePicker
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -136,12 +132,8 @@ class AdditionalLessonsFragment :
|
|||||||
binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAddAdditionalLessonDialog(currentDate: LocalDate) {
|
override fun showAddAdditionalLessonDialog() {
|
||||||
(activity as? MainActivity)?.showDialogFragment(
|
(activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance())
|
||||||
AdditionalLessonAddDialog.newInstance(
|
|
||||||
currentDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showDatePickerDialog(selectedDate: LocalDate) {
|
override fun showDatePickerDialog(selectedDate: LocalDate) {
|
||||||
|
@ -1,27 +1,14 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetable.additional
|
package io.github.wulkanowy.ui.modules.timetable.additional
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||||
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.onResourceNotLoading
|
|
||||||
import io.github.wulkanowy.data.onResourceSuccess
|
|
||||||
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.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
|
|
||||||
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.isHolidays
|
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.nextSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.previousSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -35,14 +22,11 @@ class AdditionalLessonsPresenter @Inject constructor(
|
|||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
private val semesterRepository: SemesterRepository,
|
private val semesterRepository: SemesterRepository,
|
||||||
private val timetableRepository: TimetableRepository,
|
private val timetableRepository: TimetableRepository,
|
||||||
private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
|
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
) : BasePresenter<AdditionalLessonsView>(errorHandler, studentRepository) {
|
) : BasePresenter<AdditionalLessonsView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay
|
private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay
|
||||||
|
|
||||||
private var isWeekendHasLessons: Boolean = false
|
|
||||||
|
|
||||||
lateinit var currentDate: LocalDate
|
lateinit var currentDate: LocalDate
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@ -59,18 +43,12 @@ class AdditionalLessonsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPreviousDay() {
|
fun onPreviousDay() {
|
||||||
val date = if (isWeekendHasLessons) {
|
loadData(currentDate.previousSchoolDay)
|
||||||
currentDate.minusDays(1)
|
|
||||||
} else currentDate.previousSchoolDay
|
|
||||||
loadData(date)
|
|
||||||
reloadView()
|
reloadView()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextDay() {
|
fun onNextDay() {
|
||||||
val date = if (isWeekendHasLessons) {
|
loadData(currentDate.nextSchoolDay)
|
||||||
currentDate.plusDays(1)
|
|
||||||
} else currentDate.nextSchoolDay
|
|
||||||
loadData(date)
|
|
||||||
reloadView()
|
reloadView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +57,7 @@ class AdditionalLessonsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onAdditionalLessonAddButtonClicked() {
|
fun onAdditionalLessonAddButtonClicked() {
|
||||||
view?.showAddAdditionalLessonDialog(currentDate)
|
view?.showAddAdditionalLessonDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDateSet(year: Int, month: Int, day: Int) {
|
fun onDateSet(year: Int, month: Int, day: Int) {
|
||||||
@ -153,8 +131,6 @@ class AdditionalLessonsPresenter @Inject constructor(
|
|||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
|
||||||
isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester, currentDate)
|
|
||||||
timetableRepository.getTimetable(
|
timetableRepository.getTimetable(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
|
@ -36,7 +36,7 @@ interface AdditionalLessonsView : BaseView {
|
|||||||
|
|
||||||
fun showDatePickerDialog(selectedDate: LocalDate)
|
fun showDatePickerDialog(selectedDate: LocalDate)
|
||||||
|
|
||||||
fun showAddAdditionalLessonDialog(currentDate: LocalDate)
|
fun showAddAdditionalLessonDialog()
|
||||||
|
|
||||||
fun showSuccessMessage()
|
fun showSuccessMessage()
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.timetable.additional.add
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
@ -27,12 +26,10 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
lateinit var presenter: AdditionalLessonAddPresenter
|
lateinit var presenter: AdditionalLessonAddPresenter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ARGUMENT_KEY = "additional_lesson_default_date"
|
fun newInstance() = AdditionalLessonAddDialog()
|
||||||
fun newInstance(defaultDate: LocalDate) = AdditionalLessonAddDialog().apply {
|
|
||||||
arguments = bundleOf(ARGUMENT_KEY to defaultDate.toEpochDay())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return MaterialAlertDialogBuilder(requireContext(), theme)
|
return MaterialAlertDialogBuilder(requireContext(), theme)
|
||||||
.setView(
|
.setView(
|
||||||
@ -43,13 +40,10 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
arguments?.getLong(ARGUMENT_KEY)?.let(LocalDate::ofEpochDay)?.let {
|
|
||||||
presenter.onDateSelected(it)
|
|
||||||
}
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView(selectedDate: LocalDate) {
|
override fun initView() {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ ->
|
additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ ->
|
||||||
additionalLessonDialogStart.isErrorEnabled = false
|
additionalLessonDialogStart.isErrorEnabled = false
|
||||||
@ -59,7 +53,6 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
additionalLessonDialogEnd.isErrorEnabled = false
|
additionalLessonDialogEnd.isErrorEnabled = false
|
||||||
additionalLessonDialogEnd.error = null
|
additionalLessonDialogEnd.error = null
|
||||||
}
|
}
|
||||||
additionalLessonDialogDateEdit.setText(selectedDate.toFormattedString())
|
|
||||||
additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ ->
|
additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ ->
|
||||||
additionalLessonDialogDate.isErrorEnabled = false
|
additionalLessonDialogDate.isErrorEnabled = false
|
||||||
additionalLessonDialogDate.error = null
|
additionalLessonDialogDate.error = null
|
||||||
@ -68,6 +61,7 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
additionalLessonDialogContent.isErrorEnabled = false
|
additionalLessonDialogContent.isErrorEnabled = false
|
||||||
additionalLessonDialogContent.error = null
|
additionalLessonDialogContent.error = null
|
||||||
}
|
}
|
||||||
|
|
||||||
additionalLessonDialogAdd.setOnClickListener {
|
additionalLessonDialogAdd.setOnClickListener {
|
||||||
presenter.onAddAdditionalClicked(
|
presenter.onAddAdditionalClicked(
|
||||||
start = additionalLessonDialogStartEdit.text?.toString(),
|
start = additionalLessonDialogStartEdit.text?.toString(),
|
||||||
@ -161,9 +155,7 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
timePicker.addOnPositiveButtonClickListener {
|
timePicker.addOnPositiveButtonClickListener {
|
||||||
if (isAdded) {
|
onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
|
||||||
onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parentFragmentManager.isStateSaved) {
|
if (!parentFragmentManager.isStateSaved) {
|
||||||
|
@ -10,12 +10,9 @@ import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
|
|||||||
import io.github.wulkanowy.utils.toLocalDate
|
import io.github.wulkanowy.utils.toLocalDate
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDate
|
import java.time.*
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.UUID
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AdditionalLessonAddPresenter @Inject constructor(
|
class AdditionalLessonAddPresenter @Inject constructor(
|
||||||
@ -33,7 +30,7 @@ class AdditionalLessonAddPresenter @Inject constructor(
|
|||||||
|
|
||||||
override fun onAttachView(view: AdditionalLessonAddView) {
|
override fun onAttachView(view: AdditionalLessonAddView) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
view.initView(selectedDate)
|
view.initView()
|
||||||
Timber.i("AdditionalLesson details view was initialized")
|
Timber.i("AdditionalLesson details view was initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import java.time.LocalTime
|
|||||||
|
|
||||||
interface AdditionalLessonAddView : BaseView {
|
interface AdditionalLessonAddView : BaseView {
|
||||||
|
|
||||||
fun initView(selectedDate: LocalDate)
|
fun initView()
|
||||||
|
|
||||||
fun closeDialog()
|
fun closeDialog()
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ 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.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.data.repositories.isEndDateReached
|
|
||||||
import io.github.wulkanowy.data.toFirstResult
|
import io.github.wulkanowy.data.toFirstResult
|
||||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey
|
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey
|
||||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
|
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
|
||||||
@ -72,8 +71,6 @@ class TimetableWidgetFactory(
|
|||||||
|
|
||||||
items = emptyList()
|
items = emptyList()
|
||||||
|
|
||||||
if (isEndDateReached) return
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
runCatching {
|
runCatching {
|
||||||
val student = getStudent(studentId) ?: return@runBlocking
|
val student = getStudent(studentId) ?: return@runBlocking
|
||||||
@ -104,14 +101,7 @@ class TimetableWidgetFactory(
|
|||||||
private suspend fun getLessons(
|
private suspend fun getLessons(
|
||||||
student: Student, semester: Semester, date: LocalDate
|
student: Student, semester: Semester, date: LocalDate
|
||||||
): List<Timetable> {
|
): List<Timetable> {
|
||||||
val timetable = timetableRepository.getTimetable(
|
val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
|
||||||
student = student,
|
|
||||||
semester = semester,
|
|
||||||
start = date,
|
|
||||||
end = date,
|
|
||||||
forceRefresh = false,
|
|
||||||
isFromAppWidget = true
|
|
||||||
)
|
|
||||||
val lessons = timetable.toFirstResult().dataOrThrow.lessons
|
val lessons = timetable.toFirstResult().dataOrThrow.lessons
|
||||||
return lessons.sortedBy { it.start }
|
return lessons.sortedBy { it.start }
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package io.github.wulkanowy.utils
|
|
||||||
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class AppWidgetUpdater @Inject constructor(
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val appWidgetManager: AppWidgetManager
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun updateAllAppWidgetsByProvider(providerClass: KClass<out BroadcastReceiver>) {
|
|
||||||
try {
|
|
||||||
val ids = appWidgetManager.getAppWidgetIds(ComponentName(context, providerClass.java))
|
|
||||||
if (ids.isEmpty()) return
|
|
||||||
|
|
||||||
val intent = Intent(context, providerClass.java)
|
|
||||||
.apply {
|
|
||||||
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.sendBroadcast(intent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "Failed to update all widgets for provider $providerClass")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,19 +10,19 @@ import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory
|
|||||||
* (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf)
|
* (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
inline val AttendanceSummary.allPresences: Int
|
private inline val AttendanceSummary.allPresences: Double
|
||||||
get() = presence + absenceForSchoolReasons + lateness + latenessExcused
|
get() = presence.toDouble() + absenceForSchoolReasons + lateness + latenessExcused
|
||||||
|
|
||||||
inline val AttendanceSummary.allAbsences: Int
|
private inline val AttendanceSummary.allAbsences: Double
|
||||||
get() = absence + absenceExcused
|
get() = absence.toDouble() + absenceExcused
|
||||||
|
|
||||||
inline val Attendance.isExcusableOrNotExcused: Boolean
|
inline val Attendance.isExcusableOrNotExcused: Boolean
|
||||||
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
|
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
|
||||||
|
|
||||||
fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences.toDouble(), allAbsences.toDouble())
|
fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
|
||||||
|
|
||||||
fun List<AttendanceSummary>.calculatePercentage(): Double {
|
fun List<AttendanceSummary>.calculatePercentage(): Double {
|
||||||
return calculatePercentage(sumOf { it.allPresences.toDouble() }, sumOf { it.allAbsences.toDouble() })
|
return calculatePercentage(sumOf { it.allPresences }, sumOf { it.allAbsences })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculatePercentage(presence: Double, absence: Double): Double {
|
private fun calculatePercentage(presence: Double, absence: Double): Double {
|
||||||
|
@ -30,10 +30,6 @@ fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): Strin
|
|||||||
return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
|
return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshKey(name: String): String {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
class AutoRefreshHelper @Inject constructor(
|
class AutoRefreshHelper @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val sharedPref: SharedPrefProvider
|
private val sharedPref: SharedPrefProvider
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
package io.github.wulkanowy.utils
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom alternative to androidx.recyclerview.widget.ListAdapter. ListAdapter is asynchronous which
|
|
||||||
* caused data race problems in views when a Resource.Error arrived shortly after
|
|
||||||
* Resource.Intermediate/Success - occasionally in that case the user could see both the Resource's
|
|
||||||
* data and an error message one on top of the other. This is synchronized by design to avoid that
|
|
||||||
* problem, however it retains the quality of life improvements of the original.
|
|
||||||
*/
|
|
||||||
abstract class SyncListAdapter<T : Any, VH : RecyclerView.ViewHolder> private constructor(
|
|
||||||
private val updateStrategy: SyncListAdapter<T, VH>.(List<T>) -> Unit
|
|
||||||
) : RecyclerView.Adapter<VH>() {
|
|
||||||
|
|
||||||
constructor(differ: DiffUtil.ItemCallback<T>) : this({ newItems ->
|
|
||||||
val diffResult = DiffUtil.calculateDiff(toCallback(differ, items, newItems))
|
|
||||||
items = newItems
|
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
})
|
|
||||||
|
|
||||||
var items = emptyList<T>()
|
|
||||||
private set
|
|
||||||
|
|
||||||
final override fun getItemCount() = items.size
|
|
||||||
|
|
||||||
fun getItem(position: Int): T {
|
|
||||||
return items[position]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates all items, same as submitList, however also disables animations temporarily.
|
|
||||||
* This prevents a flashing effect on some views. Should be used in favor of submitList when
|
|
||||||
* all data is changed (e.g. the selected day changes in timetable causing all lessons to change).
|
|
||||||
*/
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
fun recreate(data: List<T>) {
|
|
||||||
items = data
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun submitList(data: List<T>) {
|
|
||||||
updateStrategy(data.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : Any> toCallback(
|
|
||||||
itemCallback: DiffUtil.ItemCallback<T>,
|
|
||||||
old: List<T>,
|
|
||||||
new: List<T>,
|
|
||||||
) = object : DiffUtil.Callback() {
|
|
||||||
override fun getOldListSize() = old.size
|
|
||||||
|
|
||||||
override fun getNewListSize() = new.size
|
|
||||||
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
itemCallback.areItemsTheSame(old[oldItemPosition], new[newItemPosition])
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
itemCallback.areContentsTheSame(old[oldItemPosition], new[newItemPosition])
|
|
||||||
|
|
||||||
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
itemCallback.getChangePayload(old[oldItemPosition], new[newItemPosition])
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +1,14 @@
|
|||||||
package io.github.wulkanowy.utils
|
package io.github.wulkanowy.utils
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.DayOfWeek.FRIDAY
|
import java.time.*
|
||||||
import java.time.DayOfWeek.MONDAY
|
import java.time.DayOfWeek.*
|
||||||
import java.time.DayOfWeek.SATURDAY
|
|
||||||
import java.time.DayOfWeek.SUNDAY
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.Month
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.temporal.TemporalAdjusters.firstInMonth
|
import java.time.temporal.TemporalAdjusters.*
|
||||||
import java.time.temporal.TemporalAdjusters.next
|
import java.util.*
|
||||||
import java.time.temporal.TemporalAdjusters.previous
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy"
|
private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy"
|
||||||
|
|
||||||
fun getDefaultLocaleWithFallback(): Locale {
|
|
||||||
val locale = Locale.getDefault()
|
|
||||||
if (locale.language == "csb") {
|
|
||||||
return Locale.forLanguageTag("pl")
|
|
||||||
}
|
|
||||||
return locale
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LocalDate.toTimestamp(): Long = atStartOfDay()
|
fun LocalDate.toTimestamp(): Long = atStartOfDay()
|
||||||
.toInstant(ZoneOffset.UTC)
|
.toInstant(ZoneOffset.UTC)
|
||||||
.toEpochMilli()
|
.toEpochMilli()
|
||||||
@ -41,7 +23,7 @@ fun String.toLocalDate(format: String = DEFAULT_DATE_PATTERN): LocalDate =
|
|||||||
LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
|
LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
|
||||||
|
|
||||||
fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String =
|
fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String =
|
||||||
format(DateTimeFormatter.ofPattern(pattern, getDefaultLocaleWithFallback()))
|
format(DateTimeFormatter.ofPattern(pattern))
|
||||||
|
|
||||||
fun Instant.toFormattedString(
|
fun Instant.toFormattedString(
|
||||||
pattern: String = DEFAULT_DATE_PATTERN,
|
pattern: String = DEFAULT_DATE_PATTERN,
|
||||||
@ -49,7 +31,7 @@ fun Instant.toFormattedString(
|
|||||||
): String = atZone(tz).format(DateTimeFormatter.ofPattern(pattern))
|
): String = atZone(tz).format(DateTimeFormatter.ofPattern(pattern))
|
||||||
|
|
||||||
fun Month.getFormattedName(): String {
|
fun Month.getFormattedName(): String {
|
||||||
val formatter = SimpleDateFormat("LLLL", getDefaultLocaleWithFallback())
|
val formatter = SimpleDateFormat("LLLL", Locale.getDefault())
|
||||||
|
|
||||||
val date = LocalDateTime.now().withMonth(value)
|
val date = LocalDateTime.now().withMonth(value)
|
||||||
return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalise()
|
return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalise()
|
||||||
@ -94,7 +76,7 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline val LocalDate.weekDayName: String
|
inline val LocalDate.weekDayName: String
|
||||||
get() = format(DateTimeFormatter.ofPattern("EEEE", getDefaultLocaleWithFallback()))
|
get() = format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
|
||||||
|
|
||||||
inline val LocalDate.monday: LocalDate get() = with(MONDAY)
|
inline val LocalDate.monday: LocalDate get() = with(MONDAY)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ Zvýrazněné vlastnosti a funkce:
|
|||||||
- šťastné číslo,
|
- šťastné číslo,
|
||||||
- náhled na další a dokončené lekce,
|
- náhled na další a dokončené lekce,
|
||||||
- tmavý motiv,
|
- tmavý motiv,
|
||||||
- volitelné reklamy,
|
- žádné reklamy,
|
||||||
- offline režim,
|
- offline režim,
|
||||||
- upozornění.
|
- upozornění.
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ Wyróżnione cechy i funkcje:
|
|||||||
- szczęśliwy numerek,
|
- szczęśliwy numerek,
|
||||||
- podgląd lekcji dodatkowych i zrealizowanych,
|
- podgląd lekcji dodatkowych i zrealizowanych,
|
||||||
- ciemny motyw.
|
- ciemny motyw.
|
||||||
- opcjonalne reklam,
|
- brak reklam,
|
||||||
- tryb offline,
|
- tryb offline,
|
||||||
- powiadomienia.
|
- powiadomienia.
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 445 KiB |
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 363 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 270 KiB |
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 469 KiB |
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 346 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 293 KiB |
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 414 KiB |
@ -6,7 +6,7 @@ Zvýraznené vlastnosti a funkcie:
|
|||||||
- šťastné číslo,
|
- šťastné číslo,
|
||||||
- náhľad na ďalšie a dokončené lekcie,
|
- náhľad na ďalšie a dokončené lekcie,
|
||||||
- tmavý motív,
|
- tmavý motív,
|
||||||
- voliteľné reklamy,
|
- žiadne reklamy,
|
||||||
- offline režim,
|
- offline režim,
|
||||||
- upozornenia.
|
- upozornenia.
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
Wersja 2.7.0
|
Wersja 2.5.8
|
||||||
|
|
||||||
— naprawiliśmy ustawienia wyglądu po zmianie na język kaszubski
|
— obeszliśmy próby blokowania Wulkanowego przez firmę VULCAN, o czymś pewnie zapomnieliśmy, ale nie miejcie nam tego za złe
|
||||||
— dodaliśmy ekran końca
|
|
||||||
- naprawiliśmy ładowanie ocen z modułu Uczeń Plus
|
|
||||||
|
|
||||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19,7H9C7.9,7 7,7.9 7,9v10c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V9C21,7.9 20.1,7 19,7zM19,9v2H9V9H19zM13,15v-2h2v2H13zM15,17v2h-2v-2H15zM11,15H9v-2h2V15zM17,13h2v2h-2V13zM9,17h2v2H9V17zM17,19v-2h2v2H17zM6,17H5c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h10c1.1,0 2,0.9 2,2v1h-2V5H5v10h1V17z"/>
|
|
||||||
</vector>
|
|