forked from github/wulkanowy-mirror
Compare commits
No commits in common. "1.4.1" and "1.2.1" have entirely different histories.
331 changed files with 2432 additions and 15918 deletions
14
.github/workflows/deploy-store.yml
vendored
14
.github/workflows/deploy-store.yml
vendored
|
@ -28,16 +28,15 @@ jobs:
|
||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||||
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Upload apk to google play
|
- name: Upload apk to google play
|
||||||
env:
|
env:
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
||||||
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: Deploy to AppGallery
|
name: Deploy to AppGallery
|
||||||
|
@ -61,6 +60,7 @@ jobs:
|
||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
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/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Prepare credentials
|
- name: Prepare credentials
|
||||||
env:
|
env:
|
||||||
|
@ -68,7 +68,7 @@ jobs:
|
||||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||||
- name: Build and publish HMS version
|
- name: Build and publish HMS version
|
||||||
env:
|
env:
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
|
run: ./gradlew assembleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -186,7 +186,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2021 Wulkanowy
|
Copyright 2019 Wulkanowy
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
109
app/build.gradle
109
app/build.gradle
|
@ -1,6 +1,5 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlinx-serialization'
|
|
||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'dagger.hilt.android.plugin'
|
apply plugin: 'dagger.hilt.android.plugin'
|
||||||
|
@ -15,22 +14,23 @@ apply from: 'sonarqube.gradle'
|
||||||
apply from: 'hooks.gradle'
|
apply from: 'hooks.gradle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.github.wulkanowy"
|
applicationId "io.github.wulkanowy"
|
||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 30
|
||||||
versionCode 99
|
versionCode 94
|
||||||
versionName "1.4.1"
|
versionName "1.2.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
|
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||||
|
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
firebase_enabled: project.hasProperty("enableFirebase")
|
||||||
admob_project_id: ""
|
|
||||||
]
|
]
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
annotationProcessorOptions {
|
annotationProcessorOptions {
|
||||||
|
@ -40,14 +40,6 @@ android {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
|
||||||
|
|
||||||
if (System.env.SET_BUILD_TIMESTAMP) {
|
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
|
||||||
} else {
|
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", "1486235849000"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -70,14 +62,12 @@ android {
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
resValue "string", "app_name", "Wulkanowy DEV"
|
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,38 +76,30 @@ android {
|
||||||
productFlavors {
|
productFlavors {
|
||||||
hms {
|
hms {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [install_channel: "AppGallery"]
|
manifestPlaceholders = [
|
||||||
|
install_channel: "AppGallery"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
install_channel : "Google Play",
|
install_channel: "Google Play"
|
||||||
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
|
|
||||||
]
|
]
|
||||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fdroid {
|
fdroid {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [install_channel: "F-Droid"]
|
manifestPlaceholders = [
|
||||||
|
install_channel: "F-Droid"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playConfigs {
|
|
||||||
play { enabled.set(true) }
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle {
|
|
||||||
language {
|
|
||||||
enableSplit = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testOptions.unitTests {
|
testOptions.unitTests {
|
||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
}
|
}
|
||||||
|
@ -148,61 +130,61 @@ kapt {
|
||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
|
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||||
|
serviceAccountCredentials = file('key.p12')
|
||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'beta'
|
track = 'production'
|
||||||
updatePriority = 1
|
updatePriority = 3
|
||||||
enabled.set(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
huaweiPublish {
|
huaweiPublish {
|
||||||
instances {
|
instances {
|
||||||
hmsRelease {
|
hmsRelease {
|
||||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||||
buildFormat = "aab"
|
buildFormat = "apk"
|
||||||
deployType = "draft"
|
deployType = "draft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
work_manager = "2.7.0"
|
work_manager = "2.5.0"
|
||||||
android_hilt = "1.0.0"
|
android_hilt = "1.0.0"
|
||||||
room = "2.3.0"
|
room = "2.3.0"
|
||||||
chucker = "3.5.2"
|
chucker = "3.5.2"
|
||||||
mockk = "1.12.0"
|
mockk = "1.12.0"
|
||||||
coroutines = "1.5.2"
|
moshi = "1.12.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:1.4.0"
|
implementation "io.github.wulkanowy:sdk:1.2.1"
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.7.0"
|
implementation "androidx.core:core-ktx:1.6.0"
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||||
implementation "androidx.appcompat:appcompat:1.4.0-rc01"
|
implementation "androidx.appcompat:appcompat-resources:1.3.1"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.4.0-rc01"
|
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||||
implementation "androidx.annotation:annotation:1.3.0"
|
implementation "androidx.annotation:annotation:1.2.0"
|
||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
implementation "com.github.wulkanowy:material-chips-input:2.2.0"
|
||||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
||||||
|
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room"
|
implementation "androidx.room:room-runtime:$room"
|
||||||
implementation "androidx.room:room-ktx:$room"
|
implementation "androidx.room:room-ktx:$room"
|
||||||
|
@ -216,41 +198,40 @@ 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.9.0'
|
implementation "com.squareup.moshi:moshi:$moshi"
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
|
||||||
|
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
|
||||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||||
implementation "io.coil-kt:coil:1.4.0"
|
implementation "io.coil-kt:coil:1.3.2"
|
||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:29.0.0')
|
playImplementation platform('com.google.firebase:firebase-bom:28.4.0')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
playImplementation 'com.google.android.play:core:1.10.2'
|
playImplementation 'com.google.android.play:core:1.10.1'
|
||||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||||
playImplementation 'com.google.android.gms:play-services-ads:20.4.0'
|
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300'
|
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
||||||
|
|
||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
|
|
||||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
||||||
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
|
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6'
|
||||||
|
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
testImplementation "io.mockk:mockk:$mockk"
|
testImplementation "io.mockk:mockk:$mockk"
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.7'
|
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||||
testImplementation "androidx.test:runner:1.4.0"
|
testImplementation "androidx.test:runner:1.4.0"
|
||||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||||
testImplementation "androidx.test:core:1.4.0"
|
testImplementation "androidx.test:core:1.4.0"
|
||||||
|
|
BIN
app/key.p12.gpg
Normal file
BIN
app/key.p12.gpg
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -38,14 +38,13 @@
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
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:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="false"
|
android:supportsRtl="false"
|
||||||
android:theme="@style/WulkanowyTheme"
|
android:theme="@style/WulkanowyTheme"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
android:exported="true"
|
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
|
@ -75,7 +74,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -85,7 +83,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -96,23 +93,6 @@
|
||||||
<service
|
<service
|
||||||
android:name=".services.widgets.TimetableWidgetService"
|
android:name=".services.widgets.TimetableWidgetService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
<service
|
|
||||||
android:name=".services.piggyback.VulcanNotificationListenerService"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.service.notification.NotificationListenerService" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
<service
|
|
||||||
android:name=".services.messaging.AppMessagingService"
|
|
||||||
android:exported="false"
|
|
||||||
tools:ignore="MissingClass">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||||
|
@ -127,7 +107,6 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||||
android:exported="true"
|
|
||||||
android:label="@string/lucky_number_title">
|
android:label="@string/lucky_number_title">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -140,9 +119,11 @@
|
||||||
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
android:exported="false"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
@ -153,44 +134,44 @@
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="install_channel"
|
||||||
|
android:value="${install_channel}" />
|
||||||
|
|
||||||
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
<!-- 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 -->
|
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||||
<provider
|
<provider
|
||||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||||
android:authorities="${applicationId}.firebaseinitprovider"
|
android:authorities="${applicationId}.firebaseinitprovider"
|
||||||
android:enabled="${firebase_enabled}"
|
android:enabled="${firebase_enabled}"
|
||||||
android:exported="false"
|
android:exported="false" />
|
||||||
tools:ignore="MissingClass" />
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="install_channel"
|
|
||||||
android:value="${install_channel}" />
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_analytics_collection_enabled"
|
android:name="firebase_analytics_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_crashlytics_collection_enabled"
|
android:name="firebase_crashlytics_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_messaging_auto_init_enabled"
|
android:name="firebase_messaging_auto_init_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||||
android:value="${firebase_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" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="push_channel" />
|
android:value="push_channel" />
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
|
||||||
android:value="${admob_project_id}" />
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
|
|
||||||
android:value="true" />
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package io.github.wulkanowy
|
package io.github.wulkanowy
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Log.DEBUG
|
import android.util.Log.DEBUG
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.VERBOSE
|
import android.util.Log.VERBOSE
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
import androidx.hilt.work.HiltWorkerFactory
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
|
@ -39,8 +41,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var analyticsHelper: AnalyticsHelper
|
lateinit var analyticsHelper: AnalyticsHelper
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageWarning")
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
FragmentManager.enableNewStateManager(false)
|
||||||
initializeAppLanguage()
|
initializeAppLanguage()
|
||||||
themeManager.applyDefaultTheme()
|
themeManager.applyDefaultTheme()
|
||||||
initLogging()
|
initLogging()
|
||||||
|
|
|
@ -2,49 +2,48 @@ package io.github.wulkanowy.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.AssetManager
|
||||||
|
import android.content.res.Resources
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.chuckerteam.chucker.api.RetentionManager
|
import com.chuckerteam.chucker.api.RetentionManager
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
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.AdminMessageService
|
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.create
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal class DataModule {
|
internal class RepositoryModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
||||||
Sdk().apply {
|
return Sdk().apply {
|
||||||
androidVersion = android.os.Build.VERSION.RELEASE
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
|
||||||
// for debug only
|
// for debug only
|
||||||
addInterceptor(chuckerInterceptor, network = true)
|
addInterceptor(
|
||||||
|
ChuckerInterceptor.Builder(context)
|
||||||
|
.collector(chuckerCollector)
|
||||||
|
.alwaysReadResponseBody(true)
|
||||||
|
.build(), network = true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -52,50 +51,13 @@ internal class DataModule {
|
||||||
fun provideChuckerCollector(
|
fun provideChuckerCollector(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
prefRepository: PreferencesRepository
|
prefRepository: PreferencesRepository
|
||||||
) = ChuckerCollector(
|
): ChuckerCollector {
|
||||||
|
return ChuckerCollector(
|
||||||
context = context,
|
context = context,
|
||||||
showNotification = prefRepository.isDebugNotificationEnable,
|
showNotification = prefRepository.isDebugNotificationEnable,
|
||||||
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
||||||
)
|
)
|
||||||
|
}
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideChuckerInterceptor(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
chuckerCollector: ChuckerCollector
|
|
||||||
) = ChuckerInterceptor.Builder(context)
|
|
||||||
.collector(chuckerCollector)
|
|
||||||
.alwaysReadResponseBody(true)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient =
|
|
||||||
OkHttpClient.Builder()
|
|
||||||
.addNetworkInterceptor(chuckerInterceptor)
|
|
||||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BASIC
|
|
||||||
})
|
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideRetrofit(
|
|
||||||
okHttpClient: OkHttpClient,
|
|
||||||
json: Json,
|
|
||||||
appInfo: AppInfo
|
|
||||||
): Retrofit = Retrofit.Builder()
|
|
||||||
.baseUrl(appInfo.messagesBaseUrl)
|
|
||||||
.client(okHttpClient)
|
|
||||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -105,6 +67,14 @@ internal class DataModule {
|
||||||
appInfo: AppInfo
|
appInfo: AppInfo
|
||||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideResources(@ApplicationContext context: Context): Resources = context.resources
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAssets(@ApplicationContext context: Context): AssetManager = context.assets
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||||
|
@ -118,9 +88,7 @@ internal class DataModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideJson() = Json {
|
fun provideMoshi() = Moshi.Builder().build()
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -234,12 +202,4 @@ internal class DataModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
package io.github.wulkanowy.data.api
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
interface AdminMessageService {
|
|
||||||
|
|
||||||
@GET("/v1.json")
|
|
||||||
suspend fun getAdminMessages(): List<AdminMessage>
|
|
||||||
}
|
|
|
@ -6,11 +6,11 @@ import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
|
@ -23,10 +23,8 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
|
@ -36,11 +34,11 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||||
|
@ -53,11 +51,9 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Notification
|
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.School
|
import io.github.wulkanowy.data.db.entities.School
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
|
||||||
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.StudentInfo
|
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||||
|
@ -99,10 +95,6 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration40
|
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration41
|
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration42
|
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration43
|
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
|
@ -142,8 +134,6 @@ import javax.inject.Singleton
|
||||||
StudentInfo::class,
|
StudentInfo::class,
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
Notification::class,
|
|
||||||
AdminMessage::class
|
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
|
@ -152,7 +142,7 @@ import javax.inject.Singleton
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 43
|
const val VERSION_SCHEMA = 39
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
|
@ -193,10 +183,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
Migration37(),
|
Migration37(),
|
||||||
Migration38(),
|
Migration38(),
|
||||||
Migration39(),
|
Migration39(),
|
||||||
Migration40(),
|
|
||||||
Migration41(sharedPrefProvider),
|
|
||||||
Migration42(),
|
|
||||||
Migration43()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
|
@ -266,8 +252,4 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||||
|
|
||||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||||
|
|
||||||
abstract val notificationDao: NotificationDao
|
|
||||||
|
|
||||||
abstract val adminMessagesDao: AdminMessageDao
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package io.github.wulkanowy.data.db
|
package io.github.wulkanowy.data.db
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import kotlinx.serialization.SerializationException
|
import com.squareup.moshi.Moshi
|
||||||
import kotlinx.serialization.decodeFromString
|
import com.squareup.moshi.Types
|
||||||
import kotlinx.serialization.encodeToString
|
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
@ -14,7 +13,15 @@ import java.util.Date
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
|
||||||
private val json = Json
|
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
|
||||||
|
|
||||||
|
private val integerListAdapter by lazy {
|
||||||
|
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stringListPairAdapter by lazy {
|
||||||
|
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
||||||
|
@ -44,25 +51,21 @@ class Converters {
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun intListToJson(list: List<Int>): String {
|
fun intListToJson(list: List<Int>): String {
|
||||||
return json.encodeToString(list)
|
return integerListAdapter.toJson(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun jsonToIntList(value: String): List<Int> {
|
fun jsonToIntList(value: String): List<Int> {
|
||||||
return json.decodeFromString(value)
|
return integerListAdapter.fromJson(value).orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
||||||
return json.encodeToString(list)
|
return stringListPairAdapter.toJson(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
|
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
|
||||||
return try {
|
return stringListPairAdapter.fromJson(value).orEmpty()
|
||||||
json.decodeFromString(value)
|
|
||||||
} catch (e: SerializationException) {
|
|
||||||
emptyList() // handle errors from old gson Pair serialized data
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,11 @@ class SharedPrefProvider @Inject constructor(
|
||||||
|
|
||||||
fun getString(key: String) = sharedPref.getString(key, null)
|
fun getString(key: String) = sharedPref.getString(key, null)
|
||||||
|
|
||||||
fun getString(key: String, defaultValue: String): String =
|
fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
|
||||||
sharedPref.getString(key, defaultValue) ?: defaultValue
|
|
||||||
|
|
||||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean =
|
fun getBoolean(key: String, defaultValue: Boolean): Boolean = sharedPref.getBoolean(key, defaultValue)
|
||||||
sharedPref.getBoolean(key, defaultValue)
|
|
||||||
|
|
||||||
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) =
|
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = sharedPref.edit(sync) { putBoolean(key, value) }
|
||||||
sharedPref.edit(sync) { putBoolean(key, value) }
|
|
||||||
|
|
||||||
fun putString(key: String, value: String?, sync: Boolean = false) {
|
fun putString(key: String, value: String?, sync: Boolean = false) {
|
||||||
sharedPref.edit(sync) { putString(key, value) }
|
sharedPref.edit(sync) { putString(key, value) }
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package io.github.wulkanowy.data.db.adapters
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonReader
|
||||||
|
import com.squareup.moshi.JsonWriter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
object PairAdapterFactory : JsonAdapter.Factory {
|
||||||
|
|
||||||
|
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||||
|
if (type !is ParameterizedType || List::class.java != type.rawType) return null
|
||||||
|
if (type.actualTypeArguments[0] != Pair::class.java) return null
|
||||||
|
|
||||||
|
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
|
||||||
|
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
|
||||||
|
|
||||||
|
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
|
||||||
|
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
|
||||||
|
|
||||||
|
return PairAdapter(listAdapter, mapAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PairAdapter(
|
||||||
|
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
|
||||||
|
private val mapAdapter: JsonAdapter<Map<String, String>>,
|
||||||
|
) : JsonAdapter<List<Pair<String, String>>>() {
|
||||||
|
|
||||||
|
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
|
||||||
|
writer.beginArray()
|
||||||
|
value?.forEach {
|
||||||
|
writer.beginObject()
|
||||||
|
writer.name("first").value(it.first)
|
||||||
|
writer.name("second").value(it.second)
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
|
||||||
|
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
|
||||||
|
else deserializeGsonPair(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for compatibility with 0.21.0
|
||||||
|
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
|
||||||
|
val map = mapAdapter.fromJson(reader) ?: return null
|
||||||
|
|
||||||
|
return map.entries.map {
|
||||||
|
it.key to it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
|
||||||
|
val list = listAdapter.fromJson(reader) ?: return null
|
||||||
|
|
||||||
|
return list.map {
|
||||||
|
require(it.size == 2) {
|
||||||
|
"pair with more or less than two elements: $list"
|
||||||
|
}
|
||||||
|
|
||||||
|
it["first"].orEmpty() to it["second"].orEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Dao
|
|
||||||
abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
|
||||||
|
|
||||||
@Query("SELECT * FROM AdminMessages")
|
|
||||||
abstract fun loadAll(): Flow<List<AdminMessage>>
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open suspend fun removeOldAndSaveNew(
|
|
||||||
oldMessages: List<AdminMessage>,
|
|
||||||
newMessages: List<AdminMessage>
|
|
||||||
) {
|
|
||||||
deleteAll(oldMessages)
|
|
||||||
insertAll(newMessages)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,11 +11,6 @@ import javax.inject.Singleton
|
||||||
@Dao
|
@Dao
|
||||||
interface AttendanceDao : BaseDao<Attendance> {
|
interface AttendanceDao : BaseDao<Attendance> {
|
||||||
|
|
||||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
|
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||||
fun loadAll(
|
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Attendance>>
|
||||||
diaryId: Int,
|
|
||||||
studentId: Int,
|
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate
|
|
||||||
): Flow<List<Attendance>>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import io.github.wulkanowy.data.db.entities.Notification
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Dao
|
|
||||||
interface NotificationDao : BaseDao<Notification> {
|
|
||||||
|
|
||||||
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
|
||||||
fun loadAll(studentId: Long): Flow<List<Notification>>
|
|
||||||
}
|
|
|
@ -14,39 +14,33 @@ import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Dao
|
@Dao
|
||||||
abstract class StudentDao {
|
interface StudentDao {
|
||||||
|
|
||||||
@Insert(onConflict = ABORT)
|
@Insert(onConflict = ABORT)
|
||||||
abstract suspend fun insertAll(student: List<Student>): List<Long>
|
suspend fun insertAll(student: List<Student>): List<Long>
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
abstract suspend fun delete(student: Student)
|
suspend fun delete(student: Student)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||||
abstract suspend fun loadCurrent(): Student?
|
suspend fun loadCurrent(): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE id = :id")
|
@Query("SELECT * FROM Students WHERE id = :id")
|
||||||
abstract suspend fun loadById(id: Long): Student?
|
suspend fun loadById(id: Long): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
abstract suspend fun loadAll(): List<Student>
|
suspend fun loadAll(): List<Student>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||||
abstract suspend fun updateCurrent(id: Long)
|
suspend fun updateCurrent(id: Long)
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 0")
|
@Query("UPDATE Students SET is_current = 0")
|
||||||
abstract suspend fun resetCurrent()
|
suspend fun resetCurrent()
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open suspend fun switchCurrent(id: Long) {
|
|
||||||
resetCurrent()
|
|
||||||
updateCurrent(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.entities
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@Entity(tableName = "AdminMessages")
|
|
||||||
data class AdminMessage(
|
|
||||||
|
|
||||||
@PrimaryKey
|
|
||||||
val id: Int,
|
|
||||||
|
|
||||||
val title: String,
|
|
||||||
|
|
||||||
val content: String,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "version_name")
|
|
||||||
val versionMin: Int? = null,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "version_max")
|
|
||||||
val versionMax: Int? = null,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "target_register_host")
|
|
||||||
val targetRegisterHost: String? = null,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "target_flavor")
|
|
||||||
val targetFlavor: String? = null,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "destination_url")
|
|
||||||
val destinationUrl: String? = null,
|
|
||||||
|
|
||||||
val priority: String,
|
|
||||||
|
|
||||||
val type: String
|
|
||||||
)
|
|
|
@ -47,7 +47,4 @@ data class Attendance(
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
@ColumnInfo(name = "is_notified")
|
|
||||||
var isNotified: Boolean = true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,4 @@ data class Homework(
|
||||||
|
|
||||||
@ColumnInfo(name = "is_notified")
|
@ColumnInfo(name = "is_notified")
|
||||||
var isNotified: Boolean = true
|
var isNotified: Boolean = true
|
||||||
|
|
||||||
@ColumnInfo(name = "is_added_by_user")
|
|
||||||
var isAddedByUser: Boolean = false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.entities
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Entity(tableName = "Notifications")
|
|
||||||
data class Notification(
|
|
||||||
|
|
||||||
@ColumnInfo(name = "student_id")
|
|
||||||
val studentId: Long,
|
|
||||||
|
|
||||||
val title: String,
|
|
||||||
|
|
||||||
val content: String,
|
|
||||||
|
|
||||||
val type: NotificationType,
|
|
||||||
|
|
||||||
val date: LocalDateTime,
|
|
||||||
|
|
||||||
val data: String? = null
|
|
||||||
) {
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
var id: Long = 0
|
|
||||||
}
|
|
|
@ -3,9 +3,10 @@ package io.github.wulkanowy.data.db.entities
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@JsonClass(generateAdapter = true)
|
||||||
@Entity(tableName = "Recipients")
|
@Entity(tableName = "Recipients")
|
||||||
data class Recipient(
|
data class Recipient(
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,4 @@ data class Timetable(
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
@ColumnInfo(name = "is_notified")
|
|
||||||
var isNotified: Boolean = true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.migrations
|
|
||||||
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
|
|
||||||
class Migration40 : Migration(39, 40) {
|
|
||||||
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL(
|
|
||||||
"""
|
|
||||||
CREATE TABLE IF NOT EXISTS `Notifications` (
|
|
||||||
`student_id` INTEGER NOT NULL,
|
|
||||||
`title` TEXT NOT NULL,
|
|
||||||
`content` TEXT NOT NULL,
|
|
||||||
`type` TEXT NOT NULL,
|
|
||||||
`date` INTEGER NOT NULL,
|
|
||||||
`data` TEXT,
|
|
||||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.migrations
|
|
||||||
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
|
|
||||||
|
|
||||||
class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {
|
|
||||||
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
migrateSharedPreferences()
|
|
||||||
database.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun migrateSharedPreferences() {
|
|
||||||
if (sharedPrefProvider.getBoolean("pref_key_expand_grade", false)) {
|
|
||||||
sharedPrefProvider.putString("pref_key_expand_grade_mode", GradeExpandMode.ALWAYS_EXPANDED.value)
|
|
||||||
}
|
|
||||||
sharedPrefProvider.delete("pref_key_expand_grade")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.migrations
|
|
||||||
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
|
|
||||||
class Migration42 : Migration(41, 42) {
|
|
||||||
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL(
|
|
||||||
"""CREATE TABLE IF NOT EXISTS `AdminMessages` (
|
|
||||||
`id` INTEGER NOT NULL,
|
|
||||||
`title` TEXT NOT NULL,
|
|
||||||
`content` TEXT NOT NULL,
|
|
||||||
`version_name` INTEGER,
|
|
||||||
`version_max` INTEGER,
|
|
||||||
`target_register_host` TEXT,
|
|
||||||
`target_flavor` TEXT,
|
|
||||||
`destination_url` TEXT,
|
|
||||||
`priority` TEXT NOT NULL,
|
|
||||||
`type` TEXT NOT NULL,
|
|
||||||
PRIMARY KEY(`id`))"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package io.github.wulkanowy.data.db.migrations
|
|
||||||
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
|
|
||||||
class Migration43 : Migration(42, 43) {
|
|
||||||
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
|
||||||
database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
package io.github.wulkanowy.data.pojos
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@Serializable
|
@JsonClass(generateAdapter = true)
|
||||||
class Contributor(
|
class Contributor(
|
||||||
val displayName: String,
|
val displayName: String,
|
||||||
val githubUsername: String
|
val githubUsername: String
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package io.github.wulkanowy.data.pojos
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageDraft(
|
data class MessageDraft(
|
||||||
val recipients: List<RecipientChipItem>,
|
val recipients: List<RecipientChipItem>,
|
||||||
val subject: String,
|
val subject: String,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.PluralsRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
|
||||||
|
sealed interface Notification {
|
||||||
|
val type: NotificationType
|
||||||
|
val startMenu: MainView.Section
|
||||||
|
val icon: Int
|
||||||
|
val titleStringRes: Int
|
||||||
|
val contentStringRes: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MultipleNotifications(
|
||||||
|
override val type: NotificationType,
|
||||||
|
override val startMenu: MainView.Section,
|
||||||
|
@DrawableRes override val icon: Int,
|
||||||
|
@PluralsRes override val titleStringRes: Int,
|
||||||
|
@PluralsRes override val contentStringRes: Int,
|
||||||
|
|
||||||
|
@PluralsRes val summaryStringRes: Int,
|
||||||
|
val lines: List<String>,
|
||||||
|
) : Notification
|
||||||
|
|
||||||
|
data class OneNotification(
|
||||||
|
override val type: NotificationType,
|
||||||
|
override val startMenu: MainView.Section,
|
||||||
|
@DrawableRes override val icon: Int,
|
||||||
|
@StringRes override val titleStringRes: Int,
|
||||||
|
@StringRes override val contentStringRes: Int,
|
||||||
|
|
||||||
|
val contentValues: List<String>,
|
||||||
|
) : Notification
|
|
@ -1,19 +0,0 @@
|
||||||
package io.github.wulkanowy.data.pojos
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
|
||||||
|
|
||||||
data class NotificationData(
|
|
||||||
val intentToStart: Intent,
|
|
||||||
val title: String,
|
|
||||||
val content: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GroupNotificationData(
|
|
||||||
val notificationDataList: List<NotificationData>,
|
|
||||||
val title: String,
|
|
||||||
val content: String,
|
|
||||||
val intentToStart: Intent,
|
|
||||||
val type: NotificationType
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.api.AdminMessageService
|
|
||||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
|
||||||
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 appInfo: AppInfo,
|
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
|
||||||
|
|
||||||
private val cacheKey = "admin_messages"
|
|
||||||
|
|
||||||
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
|
||||||
query = { adminMessageDao.loadAll() },
|
|
||||||
fetch = { adminMessageService.getAdminMessages() },
|
|
||||||
shouldFetch = {
|
|
||||||
refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
|
|
||||||
},
|
|
||||||
saveFetchResult = { oldItems, newItems ->
|
|
||||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(cacheKey)
|
|
||||||
},
|
|
||||||
showSavedOnLoading = false,
|
|
||||||
mapResult = { adminMessages ->
|
|
||||||
adminMessages.filter { adminMessage ->
|
|
||||||
val isCorrectRegister = adminMessage.targetRegisterHost?.let {
|
|
||||||
student.scrapperBaseUrl.contains(it, true)
|
|
||||||
} ?: true
|
|
||||||
val isCorrectFlavor =
|
|
||||||
adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
|
|
||||||
val isCorrectMaxVersion =
|
|
||||||
adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
|
|
||||||
val isCorrectMinVersion =
|
|
||||||
adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
|
|
||||||
|
|
||||||
isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
|
|
||||||
}.maxByOrNull { it.id }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,27 +1,25 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.res.AssetManager
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
import io.github.wulkanowy.data.pojos.Contributor
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppCreatorRepository @Inject constructor(
|
class AppCreatorRepository @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val assets: AssetManager,
|
||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider
|
||||||
private val json: Json,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getAppCreators() = withContext(dispatchers.io) {
|
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
||||||
val inputStream = context.assets.open("contributors.json").buffered()
|
val moshi = Moshi.Builder().build()
|
||||||
json.decodeFromStream<List<Contributor>>(inputStream)
|
val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
|
||||||
|
val adapter = moshi.adapter<List<Contributor>>(type)
|
||||||
|
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.sunday
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
@ -33,24 +32,10 @@ class AttendanceRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "attendance"
|
private val cacheKey = "attendance"
|
||||||
|
|
||||||
fun getAttendance(
|
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
notify: Boolean = false,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
|
||||||
)
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = {
|
|
||||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
|
||||||
},
|
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||||
|
@ -58,39 +43,19 @@ class AttendanceRepository @Inject constructor(
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||||
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
attendanceDb.insertAll(new uniqueSubtract old)
|
||||||
newAttendance.apply { if (notify) isNotified = false }
|
|
||||||
}
|
|
||||||
attendanceDb.insertAll(attendanceToAdd)
|
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
},
|
},
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getAttendanceFromDatabase(
|
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
||||||
semester: Semester,
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate
|
|
||||||
): Flow<List<Attendance>> {
|
|
||||||
return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateTimetable(timetable: List<Attendance>) {
|
|
||||||
return attendanceDb.updateAll(timetable)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun excuseForAbsence(
|
|
||||||
student: Student, semester: Semester,
|
|
||||||
absenceList: List<Attendance>, reason: String? = null
|
|
||||||
) {
|
|
||||||
val items = absenceList.map { attendance ->
|
|
||||||
Absent(
|
Absent(
|
||||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||||
timeId = attendance.timeId
|
timeId = attendance.timeId
|
||||||
)
|
)
|
||||||
}
|
}, reason)
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
|
||||||
.excuseForAbsence(items, reason)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
subjectId: Int,
|
subjectId: Int,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
},
|
},
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
|
|
@ -28,28 +28,10 @@ class CompletedLessonsRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "completed"
|
private val cacheKey = "completed"
|
||||||
|
|
||||||
fun getCompletedLessons(
|
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
|
||||||
)
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = {
|
|
||||||
completedLessonsDb.loadAll(
|
|
||||||
studentId = semester.studentId,
|
|
||||||
diaryId = semester.diaryId,
|
|
||||||
from = start.monday,
|
|
||||||
end = end.sunday
|
|
||||||
)
|
|
||||||
},
|
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
|
|
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||||
|
|
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
||||||
start: LocalDate,
|
start: LocalDate,
|
||||||
end: LocalDate,
|
end: LocalDate,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
examDb.loadAll(
|
examDb.loadAll(
|
||||||
|
|
|
@ -37,12 +37,13 @@ class GradeRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (details, summaries) ->
|
shouldFetch = { (details, summaries) ->
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
val isShouldBeRefreshed =
|
||||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
|
@ -70,8 +71,8 @@ class GradeRepository @Inject constructor(
|
||||||
newDetails: List<Grade>,
|
newDetails: List<Grade>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
val notifyBreakDate =
|
||||||
?.date ?: student.registrationDate.toLocalDate()
|
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
|
@ -88,7 +89,8 @@ class GradeRepository @Inject constructor(
|
||||||
) {
|
) {
|
||||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
val oldSummary =
|
||||||
|
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||||
summary.isPredictedGradeNotified = when {
|
summary.isPredictedGradeNotified = when {
|
||||||
summary.predictedGrade.isEmpty() -> true
|
summary.predictedGrade.isEmpty() -> true
|
||||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||||
|
|
|
@ -39,19 +39,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
private val semesterCacheKey = "grade_stats_semester"
|
private val semesterCacheKey = "grade_stats_semester"
|
||||||
private val pointsCacheKey = "grade_stats_points"
|
private val pointsCacheKey = "grade_stats_points"
|
||||||
|
|
||||||
fun getGradesPartialStatistics(
|
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
subjectName: String,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = partialMutex,
|
mutex = partialMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
|
||||||
key = getRefreshKey(partialCacheKey, semester)
|
|
||||||
)
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -86,19 +76,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesSemesterStatistics(
|
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
subjectName: String,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = semesterMutex,
|
mutex = semesterMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
|
||||||
key = getRefreshKey(semesterCacheKey, semester)
|
|
||||||
)
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -114,15 +94,13 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
val itemsWithAverage = items.map { item ->
|
val itemsWithAverage = items.map { item ->
|
||||||
item.copy().apply {
|
item.copy().apply {
|
||||||
val denominator = item.amounts.sum()
|
val denominator = item.amounts.sum()
|
||||||
average = if (denominator == 0) "" else {
|
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
||||||
(item.amounts.mapIndexed { gradeValue, amount ->
|
|
||||||
(gradeValue + 1) * amount
|
(gradeValue + 1) * amount
|
||||||
}.sum().toDouble() / denominator).let {
|
}.sum().toDouble() / denominator).let {
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
when (subjectName) {
|
when (subjectName) {
|
||||||
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
|
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
|
||||||
studentId = semester.studentId,
|
studentId = semester.studentId,
|
||||||
|
@ -131,9 +109,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||||
studentGrade = 0
|
studentGrade = 0
|
||||||
).apply {
|
).apply {
|
||||||
average = itemsWithAverage.mapNotNull {
|
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
||||||
it.average.replace(",", ".").toDoubleOrNull()
|
|
||||||
}.average().let {
|
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}).reversed()
|
}).reversed()
|
||||||
|
@ -142,17 +118,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesPointsStatistics(
|
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
subjectName: String,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = pointsMutex,
|
mutex = pointsMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -30,19 +30,16 @@ class HomeworkRepository @Inject constructor(
|
||||||
private val cacheKey = "homework"
|
private val cacheKey = "homework"
|
||||||
|
|
||||||
fun getHomework(
|
fun getHomework(
|
||||||
student: Student,
|
student: Student, semester: Semester,
|
||||||
semester: Semester,
|
start: LocalDate, end: LocalDate,
|
||||||
start: LocalDate,
|
forceRefresh: Boolean, notify: Boolean = false
|
||||||
end: LocalDate,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
notify: Boolean = false,
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
homeworkDb.loadAll(
|
homeworkDb.loadAll(
|
||||||
|
@ -61,9 +58,8 @@ class HomeworkRepository @Inject constructor(
|
||||||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||||
if (notify) it.isNotified = false
|
if (notify) it.isNotified = false
|
||||||
}
|
}
|
||||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
|
||||||
|
|
||||||
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
homeworkDb.deleteAll(old uniqueSubtract new)
|
||||||
homeworkDb.insertAll(homeWorkToSave)
|
homeworkDb.insertAll(homeWorkToSave)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
|
@ -80,8 +76,4 @@ class HomeworkRepository @Inject constructor(
|
||||||
homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
|
homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
|
||||||
|
|
||||||
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
|
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
|
||||||
|
|
||||||
suspend fun saveHomework(homework: Homework) = homeworkDb.insertAll(listOf(homework))
|
|
||||||
|
|
||||||
suspend fun deleteHomework(homework: Homework) = homeworkDb.deleteAll(listOf(homework))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,24 @@ class LoggerRepository @Inject constructor(
|
||||||
|
|
||||||
suspend fun getLastLogLines() = getLastModified().readText().split("\n")
|
suspend fun getLastLogLines() = getLastModified().readText().split("\n")
|
||||||
|
|
||||||
suspend fun getLogFiles() = withContext(dispatchers.io) {
|
suspend fun getLogFiles() = withContext(dispatchers.backgroundThread) {
|
||||||
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
|
||||||
?.filter { it.name.endsWith(".log") }!!
|
it.name.endsWith(".log")
|
||||||
|
}!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLastModified() = withContext(dispatchers.io) {
|
private suspend fun getLastModified(): File {
|
||||||
|
return withContext(dispatchers.backgroundThread) {
|
||||||
var lastModifiedTime = Long.MIN_VALUE
|
var lastModifiedTime = Long.MIN_VALUE
|
||||||
var chosenFile: File? = null
|
var chosenFile: File? = null
|
||||||
|
File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file ->
|
||||||
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
|
||||||
?.forEach { file ->
|
|
||||||
if (file.lastModified() > lastModifiedTime) {
|
if (file.lastModified() > lastModifiedTime) {
|
||||||
lastModifiedTime = file.lastModified()
|
lastModifiedTime = file.lastModified()
|
||||||
chosenFile = file
|
chosenFile = file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (chosenFile == null) throw FileNotFoundException("Log file not found")
|
||||||
chosenFile ?: throw FileNotFoundException("Log file not found")
|
chosenFile!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,17 +23,11 @@ class LuckyNumberRepository @Inject constructor(
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getLuckyNumber(
|
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
notify: Boolean = false,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||||
fetch = {
|
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||||
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
|
||||||
},
|
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (new != old) {
|
if (new != old) {
|
||||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||||
|
@ -47,11 +41,9 @@ class LuckyNumberRepository @Inject constructor(
|
||||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||||
luckyNumberDb.getAll(student.studentId, start, end)
|
luckyNumberDb.getAll(student.studentId, start, end)
|
||||||
|
|
||||||
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||||
luckyNumberDb.load(student.studentId, now()).map {
|
|
||||||
if (it?.isNotified == false) it else null
|
if (it?.isNotified == false) it else null
|
||||||
}.first()
|
}.first()
|
||||||
|
|
||||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) =
|
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||||
luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
|
@ -17,6 +18,7 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||||
|
import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Folder
|
import io.github.wulkanowy.sdk.pojo.Folder
|
||||||
import io.github.wulkanowy.sdk.pojo.SentMessage
|
import io.github.wulkanowy.sdk.pojo.SentMessage
|
||||||
|
@ -27,9 +29,6 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -43,7 +42,7 @@ class MessageRepository @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
private val sharedPrefProvider: SharedPrefProvider,
|
private val sharedPrefProvider: SharedPrefProvider,
|
||||||
private val json: Json,
|
private val moshi: Moshi,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
@ -52,18 +51,14 @@ class MessageRepository @Inject constructor(
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun getMessages(
|
fun getMessages(
|
||||||
student: Student,
|
student: Student, semester: Semester,
|
||||||
semester: Semester,
|
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
||||||
folder: MessageFolder,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
notify: Boolean = false,
|
|
||||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, student, folder)
|
getRefreshKey(cacheKey, student, folder)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -82,8 +77,7 @@ class MessageRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getMessagesWithReadByChange(
|
private fun getMessagesWithReadByChange(
|
||||||
old: List<Message>,
|
old: List<Message>, new: List<Message>,
|
||||||
new: List<Message>,
|
|
||||||
setNotified: Boolean
|
setNotified: Boolean
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||||
|
@ -102,9 +96,7 @@ class MessageRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMessage(
|
fun getMessage(
|
||||||
student: Student,
|
student: Student, message: Message, markAsRead: Boolean = false
|
||||||
message: Message,
|
|
||||||
markAsRead: Boolean = false,
|
|
||||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
checkNotNull(it, { "This message no longer exist!" })
|
checkNotNull(it, { "This message no longer exist!" })
|
||||||
|
@ -143,10 +135,8 @@ class MessageRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendMessage(
|
suspend fun sendMessage(
|
||||||
student: Student,
|
student: Student, subject: String, content: String,
|
||||||
subject: String,
|
recipients: List<Recipient>
|
||||||
content: String,
|
|
||||||
recipients: List<Recipient>,
|
|
||||||
): SentMessage = sdk.init(student).sendMessage(
|
): SentMessage = sdk.init(student).sendMessage(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
|
@ -169,9 +159,9 @@ class MessageRepository @Inject constructor(
|
||||||
|
|
||||||
var draftMessage: MessageDraft?
|
var draftMessage: MessageDraft?
|
||||||
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
||||||
?.let { json.decodeFromString(it) }
|
?.let { MessageDraftJsonAdapter(moshi).fromJson(it) }
|
||||||
set(value) = sharedPrefProvider.putString(
|
set(value) = sharedPrefProvider.putString(
|
||||||
context.getString(R.string.pref_key_message_send_draft),
|
context.getString(R.string.pref_key_message_send_draft),
|
||||||
value?.let { json.encodeToString(it) }
|
value?.let { MessageDraftJsonAdapter(moshi).toJson(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,16 +28,9 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "devices"
|
private val cacheKey = "devices"
|
||||||
|
|
||||||
fun getDevices(
|
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -27,19 +28,9 @@ class NoteRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "note"
|
private val cacheKey = "note"
|
||||||
|
|
||||||
fun getNotes(
|
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
notify: Boolean = false,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
|
||||||
getRefreshKey(cacheKey, semester)
|
|
||||||
)
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
|
||||||
import io.github.wulkanowy.data.db.entities.Notification
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class NotificationRepository @Inject constructor(
|
|
||||||
private val notificationDao: NotificationDao,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
|
||||||
|
|
||||||
suspend fun saveNotification(notification: Notification) =
|
|
||||||
notificationDao.insertAll(listOf(notification))
|
|
||||||
}
|
|
|
@ -5,23 +5,21 @@ import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
import com.fredporciuncula.flow.preferences.Preference
|
import com.fredporciuncula.flow.preferences.Preference
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.adapter
|
||||||
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.sdk.toLocalDate
|
import io.github.wulkanowy.sdk.toLocalDate
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
|
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||||
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.lang.ClassCastException
|
|
||||||
import java.lang.IllegalStateException
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -30,12 +28,16 @@ import javax.inject.Singleton
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Singleton
|
@Singleton
|
||||||
class PreferencesRepository @Inject constructor(
|
class PreferencesRepository @Inject constructor(
|
||||||
@ApplicationContext val context: Context,
|
|
||||||
private val sharedPref: SharedPreferences,
|
private val sharedPref: SharedPreferences,
|
||||||
private val flowSharedPref: FlowSharedPreferences,
|
private val flowSharedPref: FlowSharedPreferences,
|
||||||
private val json: Json,
|
@ApplicationContext val context: Context,
|
||||||
|
moshi: Moshi
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
|
||||||
|
moshi.adapter()
|
||||||
|
|
||||||
val startMenuIndex: Int
|
val startMenuIndex: Int
|
||||||
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
||||||
|
|
||||||
|
@ -59,13 +61,8 @@ class PreferencesRepository @Inject constructor(
|
||||||
R.bool.pref_default_grade_average_force_calc
|
R.bool.pref_default_grade_average_force_calc
|
||||||
)
|
)
|
||||||
|
|
||||||
val gradeExpandMode: GradeExpandMode
|
val isGradeExpandable: Boolean
|
||||||
get() = GradeExpandMode.getByValue(
|
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||||
getString(
|
|
||||||
R.string.pref_key_expand_grade_mode,
|
|
||||||
R.string.pref_default_expand_grade_mode
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val showAllSubjectsOnStatisticsList: Boolean
|
val showAllSubjectsOnStatisticsList: Boolean
|
||||||
get() = getBoolean(
|
get() = getBoolean(
|
||||||
|
@ -111,22 +108,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||||
)
|
)
|
||||||
|
|
||||||
val isUpcomingLessonsNotificationsPersistentKey =
|
|
||||||
context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent)
|
|
||||||
val isUpcomingLessonsNotificationsPersistent: Boolean
|
|
||||||
get() = getBoolean(
|
|
||||||
isUpcomingLessonsNotificationsPersistentKey,
|
|
||||||
R.bool.pref_default_notification_upcoming_lessons_persistent
|
|
||||||
)
|
|
||||||
|
|
||||||
val isNotificationPiggybackEnabledKey =
|
|
||||||
context.getString(R.string.pref_key_notifications_piggyback)
|
|
||||||
val isNotificationPiggybackEnabled: Boolean
|
|
||||||
get() = getBoolean(
|
|
||||||
R.string.pref_key_notifications_piggyback,
|
|
||||||
R.bool.pref_default_notification_piggyback
|
|
||||||
)
|
|
||||||
|
|
||||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||||
val isDebugNotificationEnable: Boolean
|
val isDebugNotificationEnable: Boolean
|
||||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||||
|
@ -195,20 +176,22 @@ class PreferencesRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
var lasSyncDate: LocalDateTime
|
var lasSyncDate: LocalDateTime
|
||||||
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
get() = getLong(
|
||||||
.toLocalDateTime()
|
R.string.pref_key_last_sync_date,
|
||||||
|
R.string.pref_default_last_sync_date
|
||||||
|
).toLocalDateTime()
|
||||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||||
|
|
||||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||||
get() {
|
get() {
|
||||||
val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
||||||
|
|
||||||
return json.decodeFromString(value)
|
return dashboardItemsPositionAdapter.fromJson(json)
|
||||||
}
|
}
|
||||||
set(value) = sharedPref.edit {
|
set(value) = sharedPref.edit {
|
||||||
putString(
|
putString(
|
||||||
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
||||||
json.encodeToString(value)
|
dashboardItemsPositionAdapter.toJson(value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +200,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
.map { set ->
|
.map { set ->
|
||||||
set.map { DashboardItem.Tile.valueOf(it) }
|
set.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +207,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
get() = selectedDashboardTilesPreference.get()
|
get() = selectedDashboardTilesPreference.get()
|
||||||
.map { DashboardItem.Tile.valueOf(it) }
|
.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
|
||||||
.toSet()
|
.toSet()
|
||||||
set(value) {
|
set(value) {
|
||||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
||||||
|
@ -249,10 +230,8 @@ class PreferencesRepository @Inject constructor(
|
||||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||||
|
|
||||||
var inAppReviewDate: LocalDate?
|
var inAppReviewDate: LocalDate?
|
||||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
||||||
?.toLocalDate()
|
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
||||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
var isAppReviewDone: Boolean
|
var isAppReviewDone: Boolean
|
||||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||||
|
@ -273,9 +252,6 @@ class PreferencesRepository @Inject constructor(
|
||||||
private fun getBoolean(id: String, default: Int) =
|
private fun getBoolean(id: String, default: Int) =
|
||||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||||
|
|
||||||
private fun getBoolean(id: Int, default: Boolean) =
|
|
||||||
sharedPref.getBoolean(context.getString(id), default)
|
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
|
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
|
||||||
|
|
|
@ -7,8 +7,6 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -17,34 +15,26 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class RecipientRepository @Inject constructor(
|
class RecipientRepository @Inject constructor(
|
||||||
private val recipientDb: RecipientDao,
|
private val recipientDb: RecipientDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val cacheKey = "recipient"
|
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
|
|
||||||
recipientDb.deleteAll(old uniqueSubtract new)
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
recipientDb.insertAll(new uniqueSubtract old)
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
||||||
|
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
|
||||||
return if (cached.isEmpty() || isExpired) {
|
|
||||||
refreshRecipients(student, unit, role)
|
refreshRecipients(student, unit, role)
|
||||||
|
|
||||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
} else cached
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
||||||
.mapToEntities(student.studentId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
||||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRecoverRequest(
|
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
||||||
url: String, symbol: String, email: String, reCaptchaResponse: String
|
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||||
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
@ -11,6 +12,7 @@ import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -28,15 +30,17 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||||
|
|
||||||
fun getSchoolAnnouncements(
|
fun getSchoolAnnouncements(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean, notify: Boolean = false
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
it.isEmpty() || forceRefresh
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(student.studentId)
|
schoolAnnouncementDb.loadAll(
|
||||||
|
student.studentId)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
|
@ -53,11 +57,9 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||||
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -16,26 +14,15 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SchoolRepository @Inject constructor(
|
class SchoolRepository @Inject constructor(
|
||||||
private val schoolDb: SchoolDao,
|
private val schoolDb: SchoolDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
private val cacheKey = "school_info"
|
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||||
|
networkBoundResource(
|
||||||
fun getSchoolInfo(
|
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it == null || forceRefresh },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
|
||||||
key = getRefreshKey(cacheKey, student)
|
|
||||||
)
|
|
||||||
it == null || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||||
|
@ -50,7 +37,6 @@ class SchoolRepository @Inject constructor(
|
||||||
} else if (old == null) {
|
} else if (old == null) {
|
||||||
schoolDb.insertAll(listOf(new))
|
schoolDb.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class SemesterRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean = false,
|
forceRefresh: Boolean = false,
|
||||||
refreshOnNoCurrent: Boolean = false
|
refreshOnNoCurrent: Boolean = false
|
||||||
) = withContext(dispatchers.io) {
|
) = withContext(dispatchers.backgroundThread) {
|
||||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||||
|
|
||||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||||
|
@ -64,7 +64,7 @@ class SemesterRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.backgroundThread) {
|
||||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,8 @@ class StudentInfoRepository @Inject constructor(
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getStudentInfo(
|
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||||
student: Student,
|
networkBoundResource(
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.withTransaction
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
@ -27,8 +25,7 @@ class StudentRepository @Inject constructor(
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
private val appInfo: AppInfo,
|
private val appInfo: AppInfo
|
||||||
private val appDatabase: AppDatabase
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||||
|
@ -66,7 +63,7 @@ class StudentRepository @Inject constructor(
|
||||||
.map {
|
.map {
|
||||||
it.apply {
|
it.apply {
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +74,7 @@ class StudentRepository @Inject constructor(
|
||||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,40 +85,35 @@ class StudentRepository @Inject constructor(
|
||||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return student
|
return student
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||||
val students = studentsWithSemesters.map { it.student }
|
val students = studentsWithSemesters.map { it.student }
|
||||||
.map {
|
.map {
|
||||||
it.apply {
|
it.apply {
|
||||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||||
password = withContext(dispatchers.io) {
|
password = withContext(dispatchers.backgroundThread) {
|
||||||
encrypt(password, context)
|
encrypt(password, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mapIndexed { index, student ->
|
|
||||||
if (index == 0) {
|
|
||||||
student.copy(isCurrent = true).apply { avatarColor = student.avatarColor }
|
|
||||||
} else student
|
|
||||||
}
|
|
||||||
|
|
||||||
appDatabase.withTransaction {
|
|
||||||
studentDb.resetCurrent()
|
|
||||||
semesterDb.insertSemesters(semesters)
|
semesterDb.insertSemesters(semesters)
|
||||||
studentDb.insertAll(students)
|
return studentDb.insertAll(students)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||||
studentDb.switchCurrent(studentWithSemesters.student.id)
|
with(studentDb) {
|
||||||
|
resetCurrent()
|
||||||
|
updateCurrent(studentWithSemesters.student.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||||
|
|
|
@ -5,8 +5,6 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
|
@ -17,24 +15,14 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SubjectRepository @Inject constructor(
|
class SubjectRepository @Inject constructor(
|
||||||
private val subjectDao: SubjectDao,
|
private val subjectDao: SubjectDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
private val cacheKey = "subjects"
|
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
||||||
|
|
||||||
fun getSubjects(
|
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean = false,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -43,8 +31,6 @@ class SubjectRepository @Inject constructor(
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.deleteAll(old uniqueSubtract new)
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
subjectDao.insertAll(new uniqueSubtract old)
|
subjectDao.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
|
@ -17,24 +15,14 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class TeacherRepository @Inject constructor(
|
class TeacherRepository @Inject constructor(
|
||||||
private val teacherDb: TeacherDao,
|
private val teacherDb: TeacherDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
private val cacheKey = "teachers"
|
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||||
|
|
||||||
fun getTeachers(
|
|
||||||
student: Student,
|
|
||||||
semester: Semester,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
) = networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
|
||||||
},
|
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -44,8 +32,6 @@ class TeacherRepository @Inject constructor(
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
teacherDb.deleteAll(old uniqueSubtract new)
|
teacherDb.deleteAll(old uniqueSubtract new)
|
||||||
teacherDb.insertAll(new uniqueSubtract old)
|
teacherDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,23 +41,18 @@ class TimetableRepository @Inject constructor(
|
||||||
private val cacheKey = "timetable"
|
private val cacheKey = "timetable"
|
||||||
|
|
||||||
fun getTimetable(
|
fun getTimetable(
|
||||||
student: Student,
|
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
||||||
semester: Semester,
|
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate,
|
|
||||||
forceRefresh: Boolean,
|
|
||||||
refreshAdditional: Boolean = false,
|
|
||||||
notify: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (timetable, additional, headers) ->
|
shouldFetch = { (timetable, additional, headers) ->
|
||||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
||||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||||
|
|
||||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||||
|
|
||||||
isNoData || forceRefresh || isExpired
|
isNoData || forceRefresh || isShouldRefresh
|
||||||
},
|
},
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -68,7 +63,7 @@ class TimetableRepository @Inject constructor(
|
||||||
timetableFull.mapToEntities(semester)
|
timetableFull.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { timetableOld, timetableNew ->
|
saveFetchResult = { timetableOld, timetableNew ->
|
||||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify)
|
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
|
||||||
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
||||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||||
|
|
||||||
|
@ -84,10 +79,8 @@ class TimetableRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getFullTimetableFromDatabase(
|
private fun getFullTimetableFromDatabase(
|
||||||
student: Student,
|
student: Student, semester: Semester,
|
||||||
semester: Semester,
|
start: LocalDate, end: LocalDate
|
||||||
start: LocalDate,
|
|
||||||
end: LocalDate,
|
|
||||||
): Flow<TimetableFull> {
|
): Flow<TimetableFull> {
|
||||||
val timetableFlow = timetableDb.loadAll(
|
val timetableFlow = timetableDb.loadAll(
|
||||||
diaryId = semester.diaryId,
|
diaryId = semester.diaryId,
|
||||||
|
@ -118,27 +111,21 @@ class TimetableRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTimetableFromDatabase(
|
|
||||||
semester: Semester,
|
|
||||||
from: LocalDate,
|
|
||||||
end: LocalDate
|
|
||||||
): Flow<List<Timetable>> {
|
|
||||||
return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateTimetable(timetable: List<Timetable>) {
|
|
||||||
return timetableDb.updateAll(timetable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun refreshTimetable(
|
private suspend fun refreshTimetable(
|
||||||
student: Student,
|
student: Student,
|
||||||
lessonsOld: List<Timetable>,
|
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
||||||
lessonsNew: List<Timetable>,
|
|
||||||
notify: Boolean
|
|
||||||
) {
|
) {
|
||||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
||||||
new.apply { if (notify) isNotified = false }
|
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
||||||
|
if (matchingOld != null) {
|
||||||
|
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
||||||
|
new.copy(
|
||||||
|
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
||||||
|
teacher = if (useOldTeacher) matchingOld.teacher
|
||||||
|
else new.teacher
|
||||||
|
)
|
||||||
|
} else new
|
||||||
}
|
}
|
||||||
|
|
||||||
timetableDb.deleteAll(lessonsToRemove)
|
timetableDb.deleteAll(lessonsToRemove)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import dagger.multibindings.IntoSet
|
||||||
import io.github.wulkanowy.services.sync.channels.Channel
|
import io.github.wulkanowy.services.sync.channels.Channel
|
||||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
|
|
||||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||||
|
@ -24,7 +23,6 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
|
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||||
|
@ -169,12 +167,4 @@ abstract class ServicesModule {
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
|
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun provideChangeTimetableChannel(channel: TimetableChangeChannel): Channel
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun provideNewAttendanceChannel(channel: NewAttendanceChannel): Channel
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.github.wulkanowy.services.alarm
|
package io.github.wulkanowy.services.alarm
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -10,13 +11,11 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
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.Status
|
import io.github.wulkanowy.data.Status
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.PendingIntentCompat
|
|
||||||
import io.github.wulkanowy.utils.flowWithResource
|
import io.github.wulkanowy.utils.flowWithResource
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
|
@ -33,15 +32,12 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var studentRepository: StudentRepository
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var preferencesRepository: PreferencesRepository
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||||
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
|
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
|
||||||
|
|
||||||
const val NOTIFICATION_ID = 2137
|
const val NOTIFICATION_ID = "id"
|
||||||
|
|
||||||
const val STUDENT_NAME = "student_name"
|
const val STUDENT_NAME = "student_name"
|
||||||
const val STUDENT_ID = "student_id"
|
const val STUDENT_ID = "student_id"
|
||||||
|
@ -71,10 +67,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
|
|
||||||
private fun prepareNotification(context: Context, intent: Intent) {
|
private fun prepareNotification(context: Context, intent: Intent) {
|
||||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||||
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
|
|
||||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||||
return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
||||||
|
@ -91,39 +87,20 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
|
|
||||||
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||||
|
|
||||||
showNotification(
|
showNotification(context, notificationId, studentName,
|
||||||
context, isPersistent, studentName,
|
|
||||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||||
context.getString(
|
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
||||||
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||||
"($room) $subject".removePrefix("()")
|
|
||||||
),
|
|
||||||
nextSubject?.let {
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_later,
|
|
||||||
"($nextRoom) $nextSubject".removePrefix("()")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification(
|
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
||||||
context: Context,
|
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
isPersistent: Boolean,
|
|
||||||
studentName: String?,
|
|
||||||
countDown: Long,
|
|
||||||
timeout: Long,
|
|
||||||
title: String,
|
|
||||||
next: String?
|
|
||||||
) {
|
|
||||||
NotificationManagerCompat.from(context)
|
|
||||||
.notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID)
|
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(next)
|
.setContentText(next)
|
||||||
.setAutoCancel(false)
|
.setAutoCancel(false)
|
||||||
|
.setOngoing(true)
|
||||||
.setWhen(countDown)
|
.setWhen(countDown)
|
||||||
.setOngoing(isPersistent)
|
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
.apply {
|
.apply {
|
||||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||||
}
|
}
|
||||||
|
@ -134,14 +111,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
it.setSummaryText(studentName)
|
it.setSummaryText(studentName)
|
||||||
it.addLine(next)
|
it.addLine(next)
|
||||||
})
|
})
|
||||||
.setContentIntent(
|
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
||||||
PendingIntent.getActivity(
|
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||||
context,
|
|
||||||
NOTIFICATION_ID,
|
|
||||||
SplashActivity.getStartIntent(context, Destination.Timetable()),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.services.alarm
|
||||||
import android.app.AlarmManager
|
import android.app.AlarmManager
|
||||||
import android.app.AlarmManager.RTC_WAKEUP
|
import android.app.AlarmManager.RTC_WAKEUP
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.app.AlarmManagerCompat
|
import androidx.core.app.AlarmManagerCompat
|
||||||
|
@ -24,13 +25,12 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.PendingIntentCompat
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -53,17 +53,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
|
|
||||||
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
||||||
val studentId = student.studentId
|
val studentId = student.studentId
|
||||||
withContext(dispatchersProvider.io) {
|
withContext(dispatchersProvider.backgroundThread) {
|
||||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||||
cancelScheduledTo(
|
cancelScheduledTo(
|
||||||
range = upcomingTime..lesson.start,
|
upcomingTime..lesson.start,
|
||||||
requestCode = getRequestCode(upcomingTime, studentId)
|
getRequestCode(upcomingTime, studentId)
|
||||||
)
|
|
||||||
cancelScheduledTo(
|
|
||||||
range = lesson.start..lesson.end,
|
|
||||||
requestCode = getRequestCode(lesson.start, studentId)
|
|
||||||
)
|
)
|
||||||
|
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||||
|
|
||||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||||
}
|
}
|
||||||
|
@ -72,31 +69,20 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
|
|
||||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||||
if (now() in range) cancelNotification()
|
if (now() in range) cancelNotification()
|
||||||
|
|
||||||
alarmManager.cancel(
|
alarmManager.cancel(
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
|
||||||
context,
|
|
||||||
requestCode,
|
|
||||||
Intent(),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelNotification() =
|
fun cancelNotification() =
|
||||||
NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||||
|
|
||||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||||
return cancelScheduled(lessons, student)
|
return cancelScheduled(lessons, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
withContext(dispatchersProvider.backgroundThread) {
|
||||||
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(dispatchersProvider.io) {
|
|
||||||
lessons.groupBy { it.date }
|
lessons.groupBy { it.date }
|
||||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||||
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
||||||
|
@ -110,26 +96,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
|
|
||||||
if (lesson.start > now()) {
|
if (lesson.start > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent = intent,
|
intent,
|
||||||
studentId = student.studentId,
|
student.studentId,
|
||||||
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
NOTIFICATION_TYPE_UPCOMING,
|
||||||
time = getUpcomingLessonTime(index, active, lesson)
|
getUpcomingLessonTime(index, active, lesson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lesson.end > now()) {
|
if (lesson.end > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent = intent,
|
intent,
|
||||||
studentId = student.studentId,
|
student.studentId,
|
||||||
notificationType = NOTIFICATION_TYPE_CURRENT,
|
NOTIFICATION_TYPE_CURRENT,
|
||||||
time = lesson.start
|
lesson.start
|
||||||
)
|
)
|
||||||
if (active.lastIndex == index) {
|
if (active.lastIndex == index) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent = intent,
|
intent,
|
||||||
studentId = student.studentId,
|
student.studentId,
|
||||||
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||||
time = lesson.end
|
lesson.end
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,20 +143,17 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
notificationType: Int,
|
notificationType: Int,
|
||||||
time: LocalDateTime
|
time: LocalDateTime
|
||||||
) {
|
) {
|
||||||
try {
|
|
||||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||||
|
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
it.putExtra(LESSON_TYPE, notificationType)
|
it.putExtra(LESSON_TYPE, notificationType)
|
||||||
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
|
}, FLAG_UPDATE_CURRENT)
|
||||||
)
|
)
|
||||||
Timber.d(
|
Timber.d(
|
||||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||||
intent.getStringExtra(LESSON_TITLE)
|
intent.getStringExtra(LESSON_TITLE)
|
||||||
}, start: $time, student: $studentId"
|
}, start: $time, student: $studentId"
|
||||||
)
|
)
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
Timber.e(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package io.github.wulkanowy.services.piggyback
|
|
||||||
|
|
||||||
import android.service.notification.NotificationListenerService
|
|
||||||
import android.service.notification.StatusBarNotification
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class VulcanNotificationListenerService : NotificationListenerService() {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var syncManager: SyncManager
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var preferenceRepository: PreferencesRepository
|
|
||||||
|
|
||||||
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
|
|
||||||
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
|
|
||||||
syncManager.startOneTimeSyncWorker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package io.github.wulkanowy.services.shortcuts
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
|
|
||||||
|
|
||||||
private val destinations = mapOf(
|
|
||||||
"grade" to Destination.Grade,
|
|
||||||
"attendance" to Destination.Attendance,
|
|
||||||
"exam" to Destination.Exam,
|
|
||||||
"timetable" to Destination.Timetable()
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
|
||||||
initializeShortcuts()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDestination(intent: Intent) =
|
|
||||||
destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)]
|
|
||||||
|
|
||||||
private fun initializeShortcuts() {
|
|
||||||
val shortcutsInfo = listOf(
|
|
||||||
ShortcutInfoCompat.Builder(context, "grade_shortcut")
|
|
||||||
.setShortLabel(context.getString(R.string.grade_title))
|
|
||||||
.setLongLabel(context.getString(R.string.grade_title))
|
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
|
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
|
||||||
.apply {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "attendance_shortcut")
|
|
||||||
.setShortLabel(context.getString(R.string.attendance_title))
|
|
||||||
.setLongLabel(context.getString(R.string.attendance_title))
|
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
|
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
|
||||||
.apply {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "exam_shortcut")
|
|
||||||
.setShortLabel(context.getString(R.string.exam_title))
|
|
||||||
.setLongLabel(context.getString(R.string.exam_title))
|
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
|
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
|
||||||
.apply {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "timetable_shortcut")
|
|
||||||
.setShortLabel(context.getString(R.string.timetable_title))
|
|
||||||
.setLongLabel(context.getString(R.string.timetable_title))
|
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
|
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
|
||||||
.apply {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -57,24 +57,18 @@ class SyncManager @Inject constructor(
|
||||||
|
|
||||||
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||||
val serviceInterval = preferencesRepository.servicesInterval
|
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||||
|
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||||
workManager.enqueueUniquePeriodicWork(
|
|
||||||
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
|
||||||
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
|
||||||
.setInitialDelay(10, MINUTES)
|
.setInitialDelay(10, MINUTES)
|
||||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||||
.setConstraints(
|
.setConstraints(Constraints.Builder()
|
||||||
Constraints.Builder()
|
|
||||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||||
.build()
|
.build())
|
||||||
)
|
.build())
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
|
fun startOneTimeSyncWorker(): Flow<WorkInfo> {
|
||||||
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||||
.setInputData(
|
.setInputData(
|
||||||
Data.Builder()
|
Data.Builder()
|
||||||
|
@ -83,11 +77,7 @@ class SyncManager @Inject constructor(
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManager.enqueueUniqueWork(
|
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
||||||
"${SyncWorker::class.java.simpleName}_one_time",
|
|
||||||
ExistingWorkPolicy.REPLACE,
|
|
||||||
work
|
|
||||||
)
|
|
||||||
|
|
||||||
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,11 @@ 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.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.works.Work
|
import io.github.wulkanowy.services.sync.works.Work
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.coroutineScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@HiltWorker
|
@HiltWorker
|
||||||
|
@ -34,14 +34,13 @@ class SyncWorker @AssistedInject constructor(
|
||||||
private val semesterRepository: SemesterRepository,
|
private val semesterRepository: SemesterRepository,
|
||||||
private val works: Set<@JvmSuppressWildcards Work>,
|
private val works: Set<@JvmSuppressWildcards Work>,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val notificationManager: NotificationManagerCompat,
|
private val notificationManager: NotificationManagerCompat
|
||||||
private val dispatchersProvider: DispatchersProvider
|
|
||||||
) : CoroutineWorker(appContext, workerParameters) {
|
) : CoroutineWorker(appContext, workerParameters) {
|
||||||
|
|
||||||
override suspend fun doWork() = withContext(dispatchersProvider.io) {
|
override suspend fun doWork() = coroutineScope {
|
||||||
Timber.i("SyncWorker is starting")
|
Timber.i("SyncWorker is starting")
|
||||||
|
|
||||||
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
|
if (!studentRepository.isCurrentStudentSet()) return@coroutineScope Result.failure()
|
||||||
|
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||||
|
@ -51,12 +50,12 @@ class SyncWorker @AssistedInject constructor(
|
||||||
Timber.i("${work::class.java.simpleName} is starting")
|
Timber.i("${work::class.java.simpleName} is starting")
|
||||||
work.doWork(student, semester)
|
work.doWork(student, semester)
|
||||||
Timber.i("${work::class.java.simpleName} result: Success")
|
Timber.i("${work::class.java.simpleName} result: Success")
|
||||||
|
preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
|
||||||
null
|
null
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
|
||||||
null
|
else {
|
||||||
} else {
|
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
|
@ -71,16 +70,13 @@ class SyncWorker @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
exceptions.isNotEmpty() -> Result.retry()
|
exceptions.isNotEmpty() -> Result.retry()
|
||||||
else -> {
|
else -> Result.success()
|
||||||
preferencesRepository.lasSyncDate = LocalDateTime.now()
|
|
||||||
Result.success()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
||||||
Timber.i("SyncWorker result: $result")
|
Timber.i("SyncWorker result: $result")
|
||||||
|
|
||||||
return@withContext result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notify(result: Result) {
|
private fun notify(result: Result) {
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.channels
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@TargetApi(26)
|
|
||||||
class NewAttendanceChannel @Inject constructor(
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
@ApplicationContext private val context: Context
|
|
||||||
) : Channel {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val CHANNEL_ID = "new_attendance_channel"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create() {
|
|
||||||
notificationManager.createNotificationChannel(
|
|
||||||
NotificationChannel(
|
|
||||||
CHANNEL_ID,
|
|
||||||
context.getString(R.string.channel_new_attendance),
|
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
|
||||||
)
|
|
||||||
.apply {
|
|
||||||
enableLights(true)
|
|
||||||
enableVibration(true)
|
|
||||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.channels
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@TargetApi(26)
|
|
||||||
class TimetableChangeChannel @Inject constructor(
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
@ApplicationContext private val context: Context
|
|
||||||
) : Channel {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val CHANNEL_ID = "change_timetable_channel"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create() {
|
|
||||||
notificationManager.createNotificationChannel(
|
|
||||||
NotificationChannel(
|
|
||||||
CHANNEL_ID,
|
|
||||||
context.getString(R.string.channel_change_timetable),
|
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
|
||||||
)
|
|
||||||
.apply {
|
|
||||||
enableLights(true)
|
|
||||||
enableVibration(true)
|
|
||||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Notification
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
|
||||||
import io.github.wulkanowy.data.repositories.NotificationRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
|
||||||
import io.github.wulkanowy.utils.PendingIntentCompat
|
|
||||||
import io.github.wulkanowy.utils.getCompatBitmap
|
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class AppNotificationManager @Inject constructor(
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val studentRepository: StudentRepository,
|
|
||||||
private val notificationRepository: NotificationRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
suspend fun sendSingleNotification(
|
|
||||||
notificationData: NotificationData,
|
|
||||||
notificationType: NotificationType,
|
|
||||||
student: Student
|
|
||||||
) {
|
|
||||||
val notification = NotificationCompat.Builder(context, notificationType.channel)
|
|
||||||
.setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_all)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context,
|
|
||||||
Random.nextInt(),
|
|
||||||
notificationData.intentToStart,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setContentTitle(notificationData.title)
|
|
||||||
.setContentText(notificationData.content)
|
|
||||||
.setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.bigText(notificationData.content)
|
|
||||||
.also { builder ->
|
|
||||||
if (shouldShowStudentName()) {
|
|
||||||
builder.setSummaryText(student.nickOrName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
notificationManager.notify(Random.nextInt(), notification)
|
|
||||||
saveNotification(notificationData, notificationType, student)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
suspend fun sendMultipleNotifications(
|
|
||||||
groupNotificationData: GroupNotificationData,
|
|
||||||
student: Student
|
|
||||||
) {
|
|
||||||
val notificationType = groupNotificationData.type
|
|
||||||
val groupType = notificationType.group ?: return
|
|
||||||
val group = "${groupType}_${student.id}"
|
|
||||||
|
|
||||||
sendSummaryNotification(groupNotificationData, group, student)
|
|
||||||
|
|
||||||
groupNotificationData.notificationDataList.forEach { notificationData ->
|
|
||||||
val notification = NotificationCompat.Builder(context, notificationType.channel)
|
|
||||||
.setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_all)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context,
|
|
||||||
Random.nextInt(),
|
|
||||||
notificationData.intentToStart,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setContentTitle(notificationData.title)
|
|
||||||
.setContentText(notificationData.content)
|
|
||||||
.setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.bigText(notificationData.content)
|
|
||||||
.also { builder ->
|
|
||||||
if (shouldShowStudentName()) {
|
|
||||||
builder.setSummaryText(student.nickOrName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.setGroup(group)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
notificationManager.notify(Random.nextInt(), notification)
|
|
||||||
saveNotification(notificationData, groupNotificationData.type, student)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun sendSummaryNotification(
|
|
||||||
groupNotificationData: GroupNotificationData,
|
|
||||||
group: String,
|
|
||||||
student: Student
|
|
||||||
) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
|
||||||
|
|
||||||
val summaryNotification =
|
|
||||||
NotificationCompat.Builder(context, groupNotificationData.type.channel)
|
|
||||||
.setContentTitle(groupNotificationData.title)
|
|
||||||
.setContentText(groupNotificationData.content)
|
|
||||||
.setSmallIcon(groupNotificationData.type.icon)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setStyle(
|
|
||||||
NotificationCompat.InboxStyle()
|
|
||||||
.also { builder ->
|
|
||||||
if (shouldShowStudentName()) {
|
|
||||||
builder.setSummaryText(student.nickOrName)
|
|
||||||
}
|
|
||||||
groupNotificationData.notificationDataList.forEach {
|
|
||||||
builder.addLine(it.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context,
|
|
||||||
Random.nextInt(),
|
|
||||||
groupNotificationData.intentToStart,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setLocalOnly(true)
|
|
||||||
.setGroup(group)
|
|
||||||
.setGroupSummary(true)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val groupId = student.id * 100 + groupNotificationData.type.ordinal
|
|
||||||
notificationManager.notify(groupId.toInt(), summaryNotification)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun saveNotification(
|
|
||||||
notificationData: NotificationData,
|
|
||||||
notificationType: NotificationType,
|
|
||||||
student: Student
|
|
||||||
) {
|
|
||||||
val notificationEntity = Notification(
|
|
||||||
studentId = student.id,
|
|
||||||
title = notificationData.title,
|
|
||||||
content = notificationData.content,
|
|
||||||
type = notificationType,
|
|
||||||
date = LocalDateTime.now()
|
|
||||||
)
|
|
||||||
|
|
||||||
notificationRepository.saveNotification(notificationEntity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun shouldShowStudentName(): Boolean =
|
|
||||||
studentRepository.getSavedStudents(decryptPass = false).size > 1
|
|
||||||
}
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.PluralsRes
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
|
import io.github.wulkanowy.data.pojos.Notification
|
||||||
|
import io.github.wulkanowy.data.pojos.OneNotification
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.utils.getCompatBitmap
|
||||||
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
abstract class BaseNotification(
|
||||||
|
private val context: Context,
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
) {
|
||||||
|
|
||||||
|
protected fun sendNotification(notification: Notification, student: Student) =
|
||||||
|
when (notification) {
|
||||||
|
is OneNotification -> sendOneNotification(notification, student)
|
||||||
|
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
||||||
|
notificationManager.notify(
|
||||||
|
Random.nextInt(Int.MAX_VALUE),
|
||||||
|
getNotificationBuilder(notification).apply {
|
||||||
|
val content = context.getString(
|
||||||
|
notification.contentStringRes,
|
||||||
|
*notification.contentValues.toTypedArray()
|
||||||
|
)
|
||||||
|
setContentTitle(context.getString(notification.titleStringRes))
|
||||||
|
setContentText(content)
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student?.nickOrName)
|
||||||
|
.bigText(content)
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
||||||
|
val group = notification.type.group + student.id
|
||||||
|
val groupId = student.id * 100 + notification.type.ordinal
|
||||||
|
|
||||||
|
notification.lines.forEach { item ->
|
||||||
|
notificationManager.notify(
|
||||||
|
Random.nextInt(Int.MAX_VALUE),
|
||||||
|
getNotificationBuilder(notification).apply {
|
||||||
|
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
||||||
|
setContentText(item)
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student.nickOrName)
|
||||||
|
.bigText(item)
|
||||||
|
)
|
||||||
|
setGroup(group)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||||
|
|
||||||
|
notificationManager.notify(
|
||||||
|
groupId.toInt(),
|
||||||
|
getNotificationBuilder(notification).apply {
|
||||||
|
setSmallIcon(notification.icon)
|
||||||
|
setGroup(group)
|
||||||
|
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||||
|
setGroupSummary(true)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
||||||
|
.Builder(context, notification.type.channel)
|
||||||
|
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_all)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context, notification.startMenu.id,
|
||||||
|
MainActivity.getStartIntent(context, notification.startMenu, true),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
||||||
|
return context.resources.getQuantityString(id, value, value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,124 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ChangeTimetableNotification @Inject constructor(
|
|
||||||
private val appNotificationManager: AppNotificationManager,
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun notify(items: List<Timetable>, student: Student) {
|
|
||||||
val currentTime = LocalDateTime.now()
|
|
||||||
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
|
|
||||||
val notificationDataList = changedLessons.groupBy { it.date }
|
|
||||||
.map { (date, lessons) ->
|
|
||||||
getNotificationContents(date, lessons).map {
|
|
||||||
NotificationData(
|
|
||||||
title = context.getPlural(
|
|
||||||
R.plurals.timetable_notify_new_items_title,
|
|
||||||
1
|
|
||||||
),
|
|
||||||
content = it,
|
|
||||||
intentToStart = SplashActivity.getStartIntent(
|
|
||||||
context = context,
|
|
||||||
destination = Destination.Timetable(date)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flatten()
|
|
||||||
.ifEmpty { return }
|
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(
|
|
||||||
R.plurals.timetable_notify_new_items_title,
|
|
||||||
changedLessons.size
|
|
||||||
),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.timetable_notify_new_items_group,
|
|
||||||
changedLessons.size,
|
|
||||||
changedLessons.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()),
|
|
||||||
type = NotificationType.CHANGE_TIMETABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNotificationContents(date: LocalDate, lessons: List<Timetable>): List<String> {
|
|
||||||
val formattedDate = date.toFormattedString("EEE dd.MM")
|
|
||||||
|
|
||||||
return if (lessons.size > 2) {
|
|
||||||
listOf(
|
|
||||||
context.getPlural(
|
|
||||||
R.plurals.timetable_notify_new_items,
|
|
||||||
lessons.size,
|
|
||||||
formattedDate,
|
|
||||||
lessons.size,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
lessons.map {
|
|
||||||
buildString {
|
|
||||||
append(
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_notify_lesson,
|
|
||||||
formattedDate,
|
|
||||||
it.number,
|
|
||||||
it.subject
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (it.roomOld.isNotBlank()) {
|
|
||||||
appendLine()
|
|
||||||
append(
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_notify_change_room,
|
|
||||||
it.roomOld,
|
|
||||||
it.room
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (it.teacherOld.isNotBlank() && it.teacher != it.teacherOld) {
|
|
||||||
appendLine()
|
|
||||||
append(
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_notify_change_teacher,
|
|
||||||
it.teacherOld,
|
|
||||||
it.teacher
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (it.subjectOld.isNotBlank()) {
|
|
||||||
appendLine()
|
|
||||||
append(
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_notify_change_subject,
|
|
||||||
it.subjectOld,
|
|
||||||
it.subject
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (it.info.isNotBlank()) {
|
|
||||||
appendLine()
|
|
||||||
append(it.info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.descriptionRes
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class NewAttendanceNotification @Inject constructor(
|
|
||||||
private val appNotificationManager: AppNotificationManager,
|
|
||||||
@ApplicationContext private val context: Context
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun notify(items: List<Attendance>, student: Student) {
|
|
||||||
val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
|
|
||||||
.map {
|
|
||||||
val description = context.getString(it.descriptionRes)
|
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description"
|
|
||||||
}
|
|
||||||
.ifEmpty { return }
|
|
||||||
|
|
||||||
val notificationDataList = lines.map {
|
|
||||||
NotificationData(
|
|
||||||
title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1),
|
|
||||||
content = it,
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(
|
|
||||||
R.plurals.attendance_notify_new_items_title,
|
|
||||||
notificationDataList.size
|
|
||||||
),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.attendance_notify_new_items,
|
|
||||||
notificationDataList.size,
|
|
||||||
notificationDataList.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance),
|
|
||||||
type = NotificationType.NEW_ATTENDANCE
|
|
||||||
)
|
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +1,38 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewConferenceNotification @Inject constructor(
|
class NewConferenceNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<Conference>, student: Student) {
|
fun notify(items: List<Conference>, student: Student) {
|
||||||
val today = LocalDateTime.now()
|
val today = LocalDateTime.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
.map {
|
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||||
}
|
}.ifEmpty { return }
|
||||||
.ifEmpty { return }
|
|
||||||
|
|
||||||
val notificationDataList = lines.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_CONFERENCE,
|
||||||
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
|
icon = R.drawable.ic_more_conferences,
|
||||||
content = it,
|
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
|
contentStringRes = R.plurals.conference_notify_new_items,
|
||||||
)
|
summaryStringRes = R.plurals.conference_number_item,
|
||||||
}
|
startMenu = MainView.Section.CONFERENCE,
|
||||||
|
lines = lines
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.conference_notify_new_item_title, lines.size),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.conference_notify_new_items,
|
|
||||||
lines.size,
|
|
||||||
lines.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference),
|
|
||||||
type = NotificationType.NEW_CONFERENCE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,38 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewExamNotification @Inject constructor(
|
class NewExamNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<Exam>, student: Student) {
|
fun notify(items: List<Exam>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
.map {
|
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||||
}
|
}.ifEmpty { return }
|
||||||
.ifEmpty { return }
|
|
||||||
|
|
||||||
val notificationDataList = lines.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_EXAM,
|
||||||
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
|
icon = R.drawable.ic_main_exam,
|
||||||
content = it,
|
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
contentStringRes = R.plurals.exam_notify_new_item_content,
|
||||||
)
|
summaryStringRes = R.plurals.exam_number_item,
|
||||||
}
|
startMenu = MainView.Section.EXAM,
|
||||||
|
lines = lines
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.exam_notify_new_item_title, lines.size),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.exam_notify_new_item_content,
|
|
||||||
lines.size,
|
|
||||||
lines.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
|
||||||
type = NotificationType.NEW_EXAM
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +1,66 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewGradeNotification @Inject constructor(
|
class NewGradeNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_GRADE_DETAILS,
|
||||||
title = context.getPlural(R.plurals.grade_new_items, 1),
|
icon = R.drawable.ic_stat_grade,
|
||||||
content = "${it.subject}: ${it.entry}",
|
titleStringRes = R.plurals.grade_new_items,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
contentStringRes = R.plurals.grade_notify_new_items,
|
||||||
)
|
summaryStringRes = R.plurals.grade_number_item,
|
||||||
|
startMenu = MainView.Section.GRADE,
|
||||||
|
lines = items.map {
|
||||||
|
"${it.subject}: ${it.entry}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.grade_new_items, items.size),
|
|
||||||
content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
|
||||||
type = NotificationType.NEW_GRADE_DETAILS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||||
title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
|
icon = R.drawable.ic_stat_grade,
|
||||||
content = "${it.subject}: ${it.predictedGrade}",
|
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
contentStringRes = R.plurals.grade_notify_new_items_predicted,
|
||||||
)
|
summaryStringRes = R.plurals.grade_number_item,
|
||||||
|
startMenu = MainView.Section.GRADE,
|
||||||
|
lines = items.map {
|
||||||
|
"${it.subject}: ${it.predictedGrade}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.grade_new_items_predicted, items.size),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.grade_notify_new_items_predicted,
|
|
||||||
items.size,
|
|
||||||
items.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
|
||||||
type = NotificationType.NEW_GRADE_PREDICTED
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_GRADE_FINAL,
|
||||||
title = context.getPlural(R.plurals.grade_new_items_final, 1),
|
icon = R.drawable.ic_stat_grade,
|
||||||
content = "${it.subject}: ${it.finalGrade}",
|
titleStringRes = R.plurals.grade_new_items_final,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
contentStringRes = R.plurals.grade_notify_new_items_final,
|
||||||
)
|
summaryStringRes = R.plurals.grade_number_item,
|
||||||
|
startMenu = MainView.Section.GRADE,
|
||||||
|
lines = items.map {
|
||||||
|
"${it.subject}: ${it.finalGrade}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.grade_new_items_final, items.size),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.grade_notify_new_items_final,
|
|
||||||
items.size,
|
|
||||||
items.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
|
||||||
type = NotificationType.NEW_GRADE_FINAL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,38 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Homework
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewHomeworkNotification @Inject constructor(
|
class NewHomeworkNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<Homework>, student: Student) {
|
fun notify(items: List<Homework>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
.map {
|
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||||
}
|
}.ifEmpty { return }
|
||||||
.ifEmpty { return }
|
|
||||||
|
|
||||||
val notificationDataList = lines.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
|
||||||
title = context.getPlural(R.plurals.homework_notify_new_item_title, 1),
|
|
||||||
content = it,
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
title = context.getPlural(R.plurals.homework_notify_new_item_title, lines.size),
|
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.homework_notify_new_item_content,
|
|
||||||
lines.size,
|
|
||||||
lines.size
|
|
||||||
),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
|
|
||||||
type = NotificationType.NEW_HOMEWORK,
|
type = NotificationType.NEW_HOMEWORK,
|
||||||
notificationDataList = notificationDataList
|
icon = R.drawable.ic_more_homework,
|
||||||
|
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||||
|
contentStringRes = R.plurals.homework_notify_new_item_content,
|
||||||
|
summaryStringRes = R.plurals.homework_number_item,
|
||||||
|
startMenu = MainView.Section.HOMEWORK,
|
||||||
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,30 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.data.pojos.OneNotification
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewLuckyNumberNotification @Inject constructor(
|
class NewLuckyNumberNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(item: LuckyNumber, student: Student) {
|
fun notify(item: LuckyNumber, student: Student) {
|
||||||
val notificationData = NotificationData(
|
val notification = OneNotification(
|
||||||
title = context.getString(R.string.lucky_number_notify_new_item_title),
|
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||||
content = context.getString(
|
icon = R.drawable.ic_stat_luckynumber,
|
||||||
R.string.lucky_number_notify_new_item,
|
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||||
item.luckyNumber.toString()
|
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||||
),
|
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber)
|
contentValues = listOf(item.luckyNumber.toString())
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendSingleNotification(
|
sendNotification(notification, student)
|
||||||
notificationData = notificationData,
|
|
||||||
notificationType = NotificationType.NEW_LUCKY_NUMBER,
|
|
||||||
student = student
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,33 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewMessageNotification @Inject constructor(
|
class NewMessageNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<Message>, student: Student) {
|
fun notify(items: List<Message>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
type = NotificationType.NEW_MESSAGE,
|
||||||
title = context.getPlural(R.plurals.message_new_items, 1),
|
icon = R.drawable.ic_stat_message,
|
||||||
content = "${it.sender}: ${it.subject}",
|
titleStringRes = R.plurals.message_new_items,
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
contentStringRes = R.plurals.message_notify_new_items,
|
||||||
)
|
summaryStringRes = R.plurals.message_number_item,
|
||||||
|
startMenu = MainView.Section.MESSAGE,
|
||||||
|
lines = items.map {
|
||||||
|
"${it.sender}: ${it.subject}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
title = context.getPlural(R.plurals.message_new_items, items.size),
|
|
||||||
content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size),
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
|
||||||
type = NotificationType.NEW_MESSAGE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,46 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
|
||||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewNoteNotification @Inject constructor(
|
class NewNoteNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<Note>, student: Student) {
|
fun notify(items: List<Note>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
val titleRes = when (NoteCategory.getByValue(it.categoryType)) {
|
type = NotificationType.NEW_NOTE,
|
||||||
|
icon = R.drawable.ic_stat_note,
|
||||||
|
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||||
NoteCategory.POSITIVE -> R.plurals.praise_new_items
|
NoteCategory.POSITIVE -> R.plurals.praise_new_items
|
||||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
|
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
|
||||||
else -> R.plurals.note_new_items
|
else -> R.plurals.note_new_items
|
||||||
|
},
|
||||||
|
contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||||
|
NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items
|
||||||
|
NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items
|
||||||
|
else -> R.plurals.note_notify_new_items
|
||||||
|
},
|
||||||
|
summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||||
|
NoteCategory.POSITIVE -> R.plurals.praise_number_item
|
||||||
|
NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item
|
||||||
|
else -> R.plurals.note_number_item
|
||||||
|
},
|
||||||
|
startMenu = MainView.Section.NOTE,
|
||||||
|
lines = items.map {
|
||||||
|
"${it.teacher}: ${it.category}"
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationData(
|
|
||||||
title = context.getPlural(titleRes, 1),
|
|
||||||
content = "${it.teacher}: ${it.category}",
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
notificationDataList = notificationDataList,
|
|
||||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
|
|
||||||
title = context.getPlural(R.plurals.note_new_items, items.size),
|
|
||||||
content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size),
|
|
||||||
type = NotificationType.NEW_NOTE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,33 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
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.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||||
import io.github.wulkanowy.data.pojos.NotificationData
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
|
||||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
|
||||||
import io.github.wulkanowy.utils.getPlural
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||||
private val appNotificationManager: AppNotificationManager,
|
@ApplicationContext private val context: Context,
|
||||||
@ApplicationContext private val context: Context
|
notificationManager: NotificationManagerCompat,
|
||||||
) {
|
) : BaseNotification(context, notificationManager) {
|
||||||
|
|
||||||
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||||
val notificationDataList = items.map {
|
val notification = MultipleNotifications(
|
||||||
NotificationData(
|
|
||||||
intentToStart = SplashActivity.getStartIntent(
|
|
||||||
context = context,
|
|
||||||
destination = Destination.SchoolAnnouncement
|
|
||||||
),
|
|
||||||
title = context.getPlural(
|
|
||||||
R.plurals.school_announcement_notify_new_item_title,
|
|
||||||
1
|
|
||||||
),
|
|
||||||
content = "${it.subject}: ${it.content}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val groupNotificationData = GroupNotificationData(
|
|
||||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||||
intentToStart = SplashActivity.getStartIntent(
|
icon = R.drawable.ic_all_about,
|
||||||
context = context,
|
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||||
destination = Destination.SchoolAnnouncement
|
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||||
),
|
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||||
title = context.getPlural(
|
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||||
R.plurals.school_announcement_notify_new_item_title,
|
lines = items.map {
|
||||||
items.size
|
"${it.subject}: ${it.content}"
|
||||||
),
|
}
|
||||||
content = context.getPlural(
|
|
||||||
R.plurals.school_announcement_notify_new_items,
|
|
||||||
items.size,
|
|
||||||
items.size
|
|
||||||
),
|
|
||||||
notificationDataList = notificationDataList
|
|
||||||
)
|
)
|
||||||
|
|
||||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
|
|
||||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||||
|
@ -10,77 +8,16 @@ import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
|
||||||
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
|
|
||||||
|
|
||||||
enum class NotificationType(
|
enum class NotificationType(val group: String, val channel: String) {
|
||||||
val group: String?,
|
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||||
val channel: String,
|
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||||
val icon: Int
|
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||||
) {
|
NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID),
|
||||||
NEW_CONFERENCE(
|
NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID),
|
||||||
group = "new_conferences_group",
|
NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID),
|
||||||
channel = NewConferencesChannel.CHANNEL_ID,
|
NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID),
|
||||||
icon = R.drawable.ic_more_conferences,
|
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||||
),
|
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||||
NEW_EXAM(
|
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||||
group = "new_exam_group",
|
|
||||||
channel = NewExamChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_main_exam
|
|
||||||
),
|
|
||||||
NEW_GRADE_DETAILS(
|
|
||||||
group = "new_grade_details_group",
|
|
||||||
channel = NewGradesChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_grade,
|
|
||||||
),
|
|
||||||
NEW_GRADE_PREDICTED(
|
|
||||||
group = "new_grade_predicted_group",
|
|
||||||
channel = NewGradesChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_grade,
|
|
||||||
),
|
|
||||||
NEW_GRADE_FINAL(
|
|
||||||
group = "new_grade_final_group",
|
|
||||||
channel = NewGradesChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_grade,
|
|
||||||
),
|
|
||||||
NEW_HOMEWORK(
|
|
||||||
group = "new_homework_group",
|
|
||||||
channel = NewHomeworkChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_more_homework,
|
|
||||||
),
|
|
||||||
NEW_LUCKY_NUMBER(
|
|
||||||
group = null,
|
|
||||||
channel = LuckyNumberChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_luckynumber,
|
|
||||||
),
|
|
||||||
NEW_MESSAGE(
|
|
||||||
group = "new_message_group",
|
|
||||||
channel = NewMessagesChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_message,
|
|
||||||
),
|
|
||||||
NEW_NOTE(
|
|
||||||
group = "new_notes_group",
|
|
||||||
channel = NewNotesChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_note
|
|
||||||
),
|
|
||||||
NEW_ANNOUNCEMENT(
|
|
||||||
group = "new_school_announcements_group",
|
|
||||||
channel = NewSchoolAnnouncementsChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_all_about
|
|
||||||
),
|
|
||||||
CHANGE_TIMETABLE(
|
|
||||||
group = "change_timetable_group",
|
|
||||||
channel = TimetableChangeChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_main_timetable
|
|
||||||
),
|
|
||||||
NEW_ATTENDANCE(
|
|
||||||
group = "new_attendance_group",
|
|
||||||
channel = NewAttendanceChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_main_attendance
|
|
||||||
),
|
|
||||||
PUSH(
|
|
||||||
group = null,
|
|
||||||
channel = PushChannel.CHANNEL_ID,
|
|
||||||
icon = R.drawable.ic_stat_all
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,40 +3,15 @@ package io.github.wulkanowy.services.sync.works
|
||||||
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.repositories.AttendanceRepository
|
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
|
import io.github.wulkanowy.utils.sunday
|
||||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceWork @Inject constructor(
|
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
||||||
private val attendanceRepository: AttendanceRepository,
|
|
||||||
private val newAttendanceNotification: NewAttendanceNotification,
|
|
||||||
private val preferencesRepository: PreferencesRepository
|
|
||||||
) : Work {
|
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
attendanceRepository.getAttendance(
|
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||||
student = student,
|
|
||||||
semester = semester,
|
|
||||||
start = now().previousOrSameSchoolDay,
|
|
||||||
end = now().previousOrSameSchoolDay,
|
|
||||||
forceRefresh = true,
|
|
||||||
notify = preferencesRepository.isNotificationsEnable
|
|
||||||
)
|
|
||||||
.waitForResult()
|
|
||||||
|
|
||||||
attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
|
|
||||||
.first()
|
|
||||||
.filterNot { it.isNotified }
|
|
||||||
.let {
|
|
||||||
if (it.isNotEmpty()) newAttendanceNotification.notify(it, student)
|
|
||||||
|
|
||||||
attendanceRepository.updateTimetable(it.onEach { attendance ->
|
|
||||||
attendance.isNotified = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.monday
|
||||||
|
import io.github.wulkanowy.utils.sunday
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
|
@ -21,13 +22,13 @@ class HomeworkWork @Inject constructor(
|
||||||
homeworkRepository.getHomework(
|
homeworkRepository.getHomework(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().nextOrSameSchoolDay,
|
start = now().monday,
|
||||||
end = now().nextOrSameSchoolDay,
|
end = now().sunday,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = preferencesRepository.isNotificationsEnable
|
notify = preferencesRepository.isNotificationsEnable
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
|
homeworkRepository.getHomeworkFromDatabase(semester, now().monday, now().sunday).first()
|
||||||
.filter { !it.isNotified }.let {
|
.filter { !it.isNotified }.let {
|
||||||
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
||||||
|
|
||||||
|
|
|
@ -2,41 +2,18 @@ package io.github.wulkanowy.services.sync.works
|
||||||
|
|
||||||
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.repositories.PreferencesRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.sunday
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimetableWork @Inject constructor(
|
class TimetableWork @Inject constructor(
|
||||||
private val timetableRepository: TimetableRepository,
|
private val timetableRepository: TimetableRepository
|
||||||
private val changeTimetableNotification: ChangeTimetableNotification,
|
|
||||||
private val preferencesRepository: PreferencesRepository
|
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
timetableRepository.getTimetable(
|
timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||||
student = student,
|
|
||||||
semester = semester,
|
|
||||||
start = now().nextOrSameSchoolDay,
|
|
||||||
end = now().nextOrSameSchoolDay,
|
|
||||||
forceRefresh = true,
|
|
||||||
notify = preferencesRepository.isNotificationsEnable
|
|
||||||
)
|
|
||||||
.waitForResult()
|
|
||||||
|
|
||||||
timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
|
|
||||||
.first()
|
|
||||||
.filterNot { it.isNotified }
|
|
||||||
.let {
|
|
||||||
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
|
|
||||||
|
|
||||||
timetableRepository.updateTimetable(it.onEach { timetable ->
|
|
||||||
timetable.isNotified = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
|
@ -37,6 +40,7 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
themeManager.applyActivityTheme(this)
|
themeManager.applyActivityTheme(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(
|
setTaskDescription(
|
||||||
|
@ -79,8 +83,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
startActivity(LoginActivity.getStartIntent(this))
|
startActivity(LoginActivity.getStartIntent(this)
|
||||||
finishAffinity()
|
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
|
@ -2,33 +2,32 @@ package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.fragment.app.FragmentPagerAdapter
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
|
||||||
|
|
||||||
class BaseFragmentPagerAdapter(
|
//TODO Use ViewPager2
|
||||||
private val fragmentManager: FragmentManager,
|
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
||||||
private val pagesCount: Int,
|
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||||
lifecycle: Lifecycle,
|
|
||||||
) : FragmentStateAdapter(fragmentManager, lifecycle), TabLayoutMediator.TabConfigurationStrategy {
|
|
||||||
|
|
||||||
lateinit var itemFactory: (position: Int) -> Fragment
|
private val pages = mutableMapOf<Fragment, String?>()
|
||||||
|
|
||||||
var titleFactory: (position: Int) -> String? = { "" }
|
|
||||||
|
|
||||||
var containerId = 0
|
var containerId = 0
|
||||||
|
|
||||||
fun getFragmentInstance(position: Int): Fragment? {
|
fun getFragmentInstance(position: Int): Fragment? {
|
||||||
require(containerId != 0) { "Container id is 0" }
|
require(containerId != 0) { "Container id is 0" }
|
||||||
return fragmentManager.findFragmentByTag("f$position")
|
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = itemFactory(position)
|
fun addFragments(fragments: List<Fragment>) {
|
||||||
|
fragments.forEach { pages[it] = null }
|
||||||
override fun getItemCount() = pagesCount
|
|
||||||
|
|
||||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
|
||||||
tab.text = titleFactory(position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFragmentsWithTitle(pages: Map<Fragment, String>) {
|
||||||
|
this.pages.putAll(pages)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int) = pages.keys.elementAt(position)
|
||||||
|
|
||||||
|
override fun getCount() = pages.size
|
||||||
|
|
||||||
|
override fun getPageTitle(position: Int) = pages.values.elementAt(position)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,27 +6,29 @@ import io.github.wulkanowy.utils.flowWithResource
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancelChildren
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
open class BasePresenter<T : BaseView>(
|
open class BasePresenter<T : BaseView>(
|
||||||
protected val errorHandler: ErrorHandler,
|
protected val errorHandler: ErrorHandler,
|
||||||
protected val studentRepository: StudentRepository
|
protected val studentRepository: StudentRepository
|
||||||
) {
|
) : CoroutineScope {
|
||||||
private val job = SupervisorJob()
|
|
||||||
|
|
||||||
protected val presenterScope = CoroutineScope(job + Dispatchers.Main)
|
private var job = Job()
|
||||||
|
|
||||||
private val childrenJobs = mutableMapOf<String, Job>()
|
private val jobs = mutableMapOf<String, Job>()
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Main + job
|
||||||
|
|
||||||
var view: T? = null
|
var view: T? = null
|
||||||
|
|
||||||
open fun onAttachView(view: T) {
|
open fun onAttachView(view: T) {
|
||||||
|
job = Job()
|
||||||
this.view = view
|
this.view = view
|
||||||
errorHandler.apply {
|
errorHandler.apply {
|
||||||
showErrorMessage = view::showError
|
showErrorMessage = view::showError
|
||||||
|
@ -62,22 +64,22 @@ open class BasePresenter<T : BaseView>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
||||||
childrenJobs[individualJobTag]?.cancel()
|
jobs[individualJobTag]?.cancel()
|
||||||
val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope)
|
val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
|
||||||
childrenJobs[individualJobTag] = job
|
jobs[individualJobTag] = job
|
||||||
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelJobs(vararg names: String) {
|
fun cancelJobs(vararg names: String) {
|
||||||
names.forEach {
|
names.forEach {
|
||||||
childrenJobs[it]?.cancel()
|
jobs[it]?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onDetachView() {
|
open fun onDetachView() {
|
||||||
job.cancelChildren()
|
|
||||||
errorHandler.clear()
|
|
||||||
view = null
|
view = null
|
||||||
|
job.cancel()
|
||||||
|
errorHandler.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.view.isGone
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.databinding.DialogErrorBinding
|
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||||
|
@ -25,6 +24,8 @@ import io.github.wulkanowy.utils.openEmailClient
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import okhttp3.internal.http2.StreamResetException
|
import okhttp3.internal.http2.StreamResetException
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
@ -63,26 +64,26 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val errorStacktrace = error.stackTraceToString()
|
val stringWriter = StringWriter().apply {
|
||||||
|
error.printStackTrace(PrintWriter(this))
|
||||||
|
}
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
|
errorDialogContent.text = stringWriter.toString()
|
||||||
with(errorDialogHorizontalScroll) {
|
with(errorDialogHorizontalScroll) {
|
||||||
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
||||||
}
|
}
|
||||||
errorDialogCopy.setOnClickListener {
|
errorDialogCopy.setOnClickListener {
|
||||||
val clip = ClipData.newPlainText("Error details", errorStacktrace)
|
val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
|
||||||
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
||||||
|
|
||||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
errorDialogCancel.setOnClickListener { dismiss() }
|
errorDialogCancel.setOnClickListener { dismiss() }
|
||||||
errorDialogReport.setOnClickListener {
|
errorDialogReport.setOnClickListener {
|
||||||
openConfirmDialog { openEmailClient(errorStacktrace) }
|
openConfirmDialog { openEmailClient(stringWriter.toString()) }
|
||||||
}
|
}
|
||||||
errorDialogHumanizedMessage.text = resources.getString(error)
|
errorDialogMessage.text = resources.getString(error)
|
||||||
errorDialogErrorMessage.text = error.localizedMessage
|
|
||||||
errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
|
|
||||||
errorDialogReport.isEnabled = when (error) {
|
errorDialogReport.isEnabled = when (error) {
|
||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is InterruptedIOException,
|
is InterruptedIOException,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.res.Resources
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
|
@ -10,7 +9,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) {
|
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
|
||||||
|
|
||||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun proceed(error: Throwable) {
|
protected open fun proceed(error: Throwable) {
|
||||||
showErrorMessage(context.resources.getString(error), error)
|
showErrorMessage(resources.getString(error), error)
|
||||||
when (error) {
|
when (error) {
|
||||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||||
|
|
|
@ -41,15 +41,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isThemeApplicable(activity: AppCompatActivity) =
|
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||||
activity.packageManager
|
return activity.packageManager
|
||||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||||
.activities
|
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||||
.singleOrNull { it.name == activity::class.java.canonicalName }
|
?.theme.let {
|
||||||
?.theme
|
|
||||||
.let {
|
|
||||||
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
||||||
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
||||||
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
package io.github.wulkanowy.ui.modules
|
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.more.MoreFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
|
||||||
import java.io.Serializable
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
sealed interface Destination : Serializable {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Type in children classes have to be as getter to avoid null in enums
|
|
||||||
https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
|
|
||||||
*/
|
|
||||||
val type: Type
|
|
||||||
|
|
||||||
val fragment: Fragment
|
|
||||||
|
|
||||||
enum class Type(val defaultDestination: Destination) {
|
|
||||||
DASHBOARD(Dashboard),
|
|
||||||
GRADE(Grade),
|
|
||||||
ATTENDANCE(Attendance),
|
|
||||||
EXAM(Exam),
|
|
||||||
TIMETABLE(Timetable()),
|
|
||||||
HOMEWORK(Homework),
|
|
||||||
NOTE(Note),
|
|
||||||
CONFERENCE(Conference),
|
|
||||||
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
|
|
||||||
SCHOOL(School),
|
|
||||||
LUCKY_NUMBER(More),
|
|
||||||
MORE(More),
|
|
||||||
MESSAGE(Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
object Dashboard : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.DASHBOARD
|
|
||||||
|
|
||||||
override val fragment get() = DashboardFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Grade : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.GRADE
|
|
||||||
|
|
||||||
override val fragment get() = GradeFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Attendance : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.ATTENDANCE
|
|
||||||
|
|
||||||
override val fragment get() = AttendanceFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Exam : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.EXAM
|
|
||||||
|
|
||||||
override val fragment get() = ExamFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Timetable(val date: LocalDate? = null) : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.TIMETABLE
|
|
||||||
|
|
||||||
override val fragment get() = TimetableFragment.newInstance(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
object Homework : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.HOMEWORK
|
|
||||||
|
|
||||||
override val fragment get() = HomeworkFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Note : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.NOTE
|
|
||||||
|
|
||||||
override val fragment get() = NoteFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Conference : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.CONFERENCE
|
|
||||||
|
|
||||||
override val fragment get() = ConferenceFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object SchoolAnnouncement : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.SCHOOL_ANNOUNCEMENT
|
|
||||||
|
|
||||||
override val fragment get() = SchoolAnnouncementFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object School : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.SCHOOL
|
|
||||||
|
|
||||||
override val fragment get() = SchoolFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object LuckyNumber : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.LUCKY_NUMBER
|
|
||||||
|
|
||||||
override val fragment get() = LuckyNumberFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object More : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.MORE
|
|
||||||
|
|
||||||
override val fragment get() = MoreFragment.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
object Message : Destination {
|
|
||||||
|
|
||||||
override val type get() = Type.MESSAGE
|
|
||||||
|
|
||||||
override val fragment get() = MessageFragment.newInstance()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -82,8 +82,7 @@ class AboutPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
view?.run {
|
view?.run {
|
||||||
updateData(
|
updateData(listOfNotNull(
|
||||||
listOfNotNull(
|
|
||||||
versionRes,
|
versionRes,
|
||||||
creatorsRes,
|
creatorsRes,
|
||||||
feedbackRes,
|
feedbackRes,
|
||||||
|
@ -94,8 +93,7 @@ class AboutPresenter @Inject constructor(
|
||||||
homepageRes,
|
homepageRes,
|
||||||
licensesRes,
|
licensesRes,
|
||||||
privacyRes
|
privacyRes
|
||||||
)
|
))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class LicensePresenter @Inject constructor(
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
flowWithResource {
|
flowWithResource {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.backgroundThread) {
|
||||||
view?.appLibraries.orEmpty()
|
view?.appLibraries.orEmpty()
|
||||||
}
|
}
|
||||||
}.onEach {
|
}.onEach {
|
||||||
|
|
|
@ -121,14 +121,10 @@ class AccountDetailsFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popViewToMain() {
|
override fun popView() {
|
||||||
(requireActivity() as MainActivity).popView(2)
|
(requireActivity() as MainActivity).popView(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popViewToAccounts() {
|
|
||||||
(requireActivity() as MainActivity).popView(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun recreateMainView() {
|
override fun recreateMainView() {
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
view?.popViewToMain()
|
view?.popView()
|
||||||
}.launch("switch")
|
}.launch("switch")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +152,11 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
syncManager.stopSyncWorker()
|
syncManager.stopSyncWorker()
|
||||||
openClearLoginView()
|
openClearLoginView()
|
||||||
}
|
}
|
||||||
studentWithSemesters?.student?.isCurrent == true -> {
|
studentWithSemesters!!.student.isCurrent -> {
|
||||||
Timber.i("Logout result: Logout student and switch to another")
|
Timber.i("Logout result: Logout student and switch to another")
|
||||||
recreateMainView()
|
recreateMainView()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> Timber.i("Logout result: Logout student")
|
||||||
Timber.i("Logout result: Logout student")
|
|
||||||
recreateMainView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status.ERROR -> {
|
Status.ERROR -> {
|
||||||
|
@ -168,11 +165,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
if (studentWithSemesters?.student?.isCurrent == true) {
|
view?.popView()
|
||||||
view?.popViewToMain()
|
|
||||||
} else {
|
|
||||||
view?.popViewToAccounts()
|
|
||||||
}
|
|
||||||
}.launch("logout")
|
}.launch("logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,7 @@ interface AccountDetailsView : BaseView {
|
||||||
|
|
||||||
fun showLogoutConfirmDialog()
|
fun showLogoutConfirmDialog()
|
||||||
|
|
||||||
fun popViewToMain()
|
fun popView()
|
||||||
|
|
||||||
fun popViewToAccounts()
|
|
||||||
|
|
||||||
fun recreateMainView()
|
fun recreateMainView()
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,12 @@ package io.github.wulkanowy.ui.modules.account.accountedit
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.graphics.drawable.RippleDrawable
|
import android.graphics.drawable.RippleDrawable
|
||||||
|
import android.graphics.drawable.StateListDrawable
|
||||||
|
import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -49,12 +52,29 @@ class AccountEditColorAdapter @Inject constructor() :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Int.createForegroundDrawable(): Drawable {
|
private fun Int.createForegroundDrawable(): Drawable =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val mask = GradientDrawable().apply {
|
val mask = GradientDrawable().apply {
|
||||||
shape = GradientDrawable.OVAL
|
shape = GradientDrawable.OVAL
|
||||||
setColor(Color.BLACK)
|
setColor(Color.BLACK)
|
||||||
}
|
}
|
||||||
return RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
|
RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
|
||||||
|
} else {
|
||||||
|
val foreground = StateListDrawable().apply {
|
||||||
|
alpha = 80
|
||||||
|
setEnterFadeDuration(250)
|
||||||
|
setExitFadeDuration(250)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mask = GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(this@createForegroundDrawable.rippleColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreground.apply {
|
||||||
|
addState(intArrayOf(android.R.attr.state_pressed), mask)
|
||||||
|
addState(intArrayOf(), ColorDrawable(Color.TRANSPARENT))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline val Int.rippleColor: Int
|
private inline val Int.rippleColor: Int
|
||||||
|
|
|
@ -9,7 +9,7 @@ import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.enums.SentExcuseStatus
|
import io.github.wulkanowy.data.enums.SentExcuseStatus
|
||||||
import io.github.wulkanowy.databinding.ItemAttendanceBinding
|
import io.github.wulkanowy.databinding.ItemAttendanceBinding
|
||||||
import io.github.wulkanowy.utils.descriptionRes
|
import io.github.wulkanowy.utils.description
|
||||||
import io.github.wulkanowy.utils.isExcusableOrNotExcused
|
import io.github.wulkanowy.utils.isExcusableOrNotExcused
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
attendanceItemNumber.text = item.number.toString()
|
attendanceItemNumber.text = item.number.toString()
|
||||||
attendanceItemSubject.text = item.subject
|
attendanceItemSubject.text = item.subject
|
||||||
attendanceItemDescription.setText(item.descriptionRes)
|
attendanceItemDescription.setText(item.description)
|
||||||
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
|
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
|
||||||
attendanceItemNumber.visibility = View.GONE
|
attendanceItemNumber.visibility = View.GONE
|
||||||
attendanceItemExcuseInfo.visibility = View.GONE
|
attendanceItemExcuseInfo.visibility = View.GONE
|
||||||
|
@ -46,7 +46,7 @@ class AttendanceAdapter @Inject constructor() :
|
||||||
onExcuseCheckboxSelect(item, checked)
|
onExcuseCheckboxSelect(item, checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it)}) {
|
when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) {
|
||||||
SentExcuseStatus.WAITING -> {
|
SentExcuseStatus.WAITING -> {
|
||||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
||||||
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue