Compare commits
No commits in common. "1.4.0" and "1.2.1" have entirely different histories.
331 changed files with 2432 additions and 15916 deletions
12
.github/workflows/deploy-store.yml
vendored
12
.github/workflows/deploy-store.yml
vendored
|
@ -28,14 +28,15 @@ jobs:
|
|||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Upload apk to google play
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||
|
||||
deploy-app-gallery:
|
||||
name: Deploy to AppGallery
|
||||
|
@ -59,6 +60,7 @@ jobs:
|
|||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Prepare credentials
|
||||
env:
|
||||
|
@ -66,7 +68,7 @@ jobs:
|
|||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||
- name: Build and publish HMS version
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
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
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 Wulkanowy
|
||||
Copyright 2019 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (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: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
|
@ -15,22 +14,23 @@ apply from: 'sonarqube.gradle'
|
|||
apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 98
|
||||
versionName "1.4.0"
|
||||
targetSdkVersion 30
|
||||
versionCode 94
|
||||
versionName "1.2.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||
admob_project_id: ""
|
||||
firebase_enabled: project.hasProperty("enableFirebase")
|
||||
]
|
||||
javaCompileOptions {
|
||||
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 {
|
||||
|
@ -70,14 +62,12 @@ android {
|
|||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
debug {
|
||||
resValue "string", "app_name", "Wulkanowy DEV"
|
||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,38 +76,30 @@ android {
|
|||
productFlavors {
|
||||
hms {
|
||||
dimension "platform"
|
||||
manifestPlaceholders = [install_channel: "AppGallery"]
|
||||
manifestPlaceholders = [
|
||||
install_channel: "AppGallery"
|
||||
]
|
||||
}
|
||||
|
||||
play {
|
||||
dimension "platform"
|
||||
manifestPlaceholders = [
|
||||
install_channel : "Google Play",
|
||||
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
|
||||
install_channel: "Google Play"
|
||||
]
|
||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
|
||||
}
|
||||
|
||||
fdroid {
|
||||
dimension "platform"
|
||||
manifestPlaceholders = [install_channel: "F-Droid"]
|
||||
manifestPlaceholders = [
|
||||
install_channel: "F-Droid"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
playConfigs {
|
||||
play { enabled.set(true) }
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
|
@ -148,61 +130,61 @@ kapt {
|
|||
}
|
||||
|
||||
play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'beta'
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
}
|
||||
|
||||
huaweiPublish {
|
||||
instances {
|
||||
hmsRelease {
|
||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||
buildFormat = "aab"
|
||||
buildFormat = "apk"
|
||||
deployType = "draft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.7.0"
|
||||
work_manager = "2.5.0"
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.3.0"
|
||||
chucker = "3.5.2"
|
||||
mockk = "1.12.0"
|
||||
coroutines = "1.5.2"
|
||||
moshi = "1.12.0"
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation "androidx.appcompat:appcompat:1.4.0-rc01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.0-rc01"
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||
implementation "androidx.appcompat:appcompat-resources:1.3.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||
implementation "androidx.annotation:annotation:1.2.0"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.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 "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.lopspower:CircularImageView:4.2.0'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
||||
|
||||
implementation "androidx.room:room-runtime:$room"
|
||||
implementation "androidx.room:room-ktx:$room"
|
||||
|
@ -216,41 +198,40 @@ dependencies {
|
|||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
|
||||
implementation "com.squareup.moshi:moshi:$moshi"
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.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 "io.coil-kt:coil:1.4.0"
|
||||
implementation "io.coil-kt:coil:1.3.2"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
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-messaging:'
|
||||
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.gms:play-services-ads:20.4.0'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$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 "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.robolectric:robolectric:4.7'
|
||||
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||
testImplementation "androidx.test:runner:1.4.0"
|
||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||
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:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
|
@ -75,7 +74,6 @@
|
|||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
|
@ -85,7 +83,6 @@
|
|||
<activity
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
|
@ -96,23 +93,6 @@
|
|||
<service
|
||||
android:name=".services.widgets.TimetableWidgetService"
|
||||
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
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
|
@ -127,7 +107,6 @@
|
|||
</receiver>
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/lucky_number_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -140,9 +119,11 @@
|
|||
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
@ -153,44 +134,44 @@
|
|||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="install_channel"
|
||||
android:value="${install_channel}" />
|
||||
|
||||
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
android:enabled="${firebase_enabled}"
|
||||
android:exported="false"
|
||||
tools:ignore="MissingClass" />
|
||||
android:exported="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="install_channel"
|
||||
android:value="${install_channel}" />
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_messaging_auto_init_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_stat_all" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
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>
|
||||
</manifest>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package io.github.wulkanowy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.util.Log.DEBUG
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import android.webkit.WebView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
|
@ -39,8 +41,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
|||
@Inject
|
||||
lateinit var analyticsHelper: AnalyticsHelper
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageWarning")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FragmentManager.enableNewStateManager(false)
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
initLogging()
|
||||
|
|
|
@ -2,49 +2,48 @@ package io.github.wulkanowy.data
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
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.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
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 java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal class DataModule {
|
||||
internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
||||
Sdk().apply {
|
||||
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
||||
return Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
@ -52,50 +51,13 @@ internal class DataModule {
|
|||
fun provideChuckerCollector(
|
||||
@ApplicationContext context: Context,
|
||||
prefRepository: PreferencesRepository
|
||||
) = ChuckerCollector(
|
||||
): ChuckerCollector {
|
||||
return ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
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
|
||||
@Provides
|
||||
|
@ -105,6 +67,14 @@ internal class DataModule {
|
|||
appInfo: 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
|
||||
@Provides
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||
|
@ -118,9 +88,7 @@ internal class DataModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideJson() = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
fun provideMoshi() = Moshi.Builder().build()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
|
@ -234,12 +202,4 @@ internal class DataModule {
|
|||
@Singleton
|
||||
@Provides
|
||||
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.JournalMode.TRUNCATE
|
||||
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.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
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.GradeDao
|
||||
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.MobileDeviceDao
|
||||
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.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
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.TimetableDao
|
||||
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.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
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.Grade
|
||||
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.MobileDevice
|
||||
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.ReportingUnit
|
||||
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.Student
|
||||
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.Migration39
|
||||
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.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
|
@ -142,8 +134,6 @@ import javax.inject.Singleton
|
|||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class,
|
||||
AdminMessage::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
|
@ -152,7 +142,7 @@ import javax.inject.Singleton
|
|||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 43
|
||||
const val VERSION_SCHEMA = 39
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
|
@ -193,10 +183,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40(),
|
||||
Migration41(sharedPrefProvider),
|
||||
Migration42(),
|
||||
Migration43()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
|
@ -266,8 +252,4 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||
|
||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||
|
||||
abstract val notificationDao: NotificationDao
|
||||
|
||||
abstract val adminMessagesDao: AdminMessageDao
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package io.github.wulkanowy.data.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
@ -14,7 +13,15 @@ import java.util.Date
|
|||
|
||||
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
|
||||
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
||||
|
@ -44,25 +51,21 @@ class Converters {
|
|||
|
||||
@TypeConverter
|
||||
fun intListToJson(list: List<Int>): String {
|
||||
return json.encodeToString(list)
|
||||
return integerListAdapter.toJson(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToIntList(value: String): List<Int> {
|
||||
return json.decodeFromString(value)
|
||||
return integerListAdapter.fromJson(value).orEmpty()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
||||
return json.encodeToString(list)
|
||||
return stringListPairAdapter.toJson(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
|
||||
return try {
|
||||
json.decodeFromString(value)
|
||||
} catch (e: SerializationException) {
|
||||
emptyList() // handle errors from old gson Pair serialized data
|
||||
}
|
||||
return stringListPairAdapter.fromJson(value).orEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,11 @@ class SharedPrefProvider @Inject constructor(
|
|||
|
||||
fun getString(key: String) = sharedPref.getString(key, null)
|
||||
|
||||
fun getString(key: String, defaultValue: String): String =
|
||||
sharedPref.getString(key, defaultValue) ?: defaultValue
|
||||
fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean =
|
||||
sharedPref.getBoolean(key, defaultValue)
|
||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean = sharedPref.getBoolean(key, defaultValue)
|
||||
|
||||
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) =
|
||||
sharedPref.edit(sync) { putBoolean(key, value) }
|
||||
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = sharedPref.edit(sync) { putBoolean(key, value) }
|
||||
|
||||
fun putString(key: String, value: String?, sync: Boolean = false) {
|
||||
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
|
||||
interface AttendanceDao : BaseDao<Attendance> {
|
||||
|
||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
|
||||
fun loadAll(
|
||||
diaryId: Int,
|
||||
studentId: Int,
|
||||
start: LocalDate,
|
||||
end: LocalDate
|
||||
): Flow<List<Attendance>>
|
||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: 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
|
||||
@Dao
|
||||
abstract class StudentDao {
|
||||
interface StudentDao {
|
||||
|
||||
@Insert(onConflict = ABORT)
|
||||
abstract suspend fun insertAll(student: List<Student>): List<Long>
|
||||
suspend fun insertAll(student: List<Student>): List<Long>
|
||||
|
||||
@Delete
|
||||
abstract suspend fun delete(student: Student)
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
abstract suspend fun loadCurrent(): Student?
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
abstract suspend fun loadById(id: Long): Student?
|
||||
suspend fun loadById(id: Long): Student?
|
||||
|
||||
@Query("SELECT * FROM Students")
|
||||
abstract suspend fun loadAll(): List<Student>
|
||||
suspend fun loadAll(): List<Student>
|
||||
|
||||
@Transaction
|
||||
@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")
|
||||
abstract suspend fun updateCurrent(id: Long)
|
||||
suspend fun updateCurrent(id: Long)
|
||||
|
||||
@Query("UPDATE Students SET is_current = 0")
|
||||
abstract suspend fun resetCurrent()
|
||||
|
||||
@Transaction
|
||||
open suspend fun switchCurrent(id: Long) {
|
||||
resetCurrent()
|
||||
updateCurrent(id)
|
||||
}
|
||||
suspend fun resetCurrent()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
var id: Long = 0
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
var isNotified: Boolean = true
|
||||
}
|
||||
|
|
|
@ -40,7 +40,4 @@ data class Homework(
|
|||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
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.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.io.Serializable
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Entity(tableName = "Recipients")
|
||||
data class Recipient(
|
||||
|
||||
|
|
|
@ -50,7 +50,4 @@ data class Timetable(
|
|||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
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
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@Serializable
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Contributor(
|
||||
val displayName: String,
|
||||
val githubUsername: String
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageDraft(
|
||||
val recipients: List<RecipientChipItem>,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import android.content.res.AssetManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.github.wulkanowy.data.pojos.Contributor
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
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.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppCreatorRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val json: Json,
|
||||
private val assets: AssetManager,
|
||||
private val dispatchers: DispatchersProvider
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun getAppCreators() = withContext(dispatchers.io) {
|
||||
val inputStream = context.assets.open("contributors.json").buffered()
|
||||
json.decodeFromStream<List<Contributor>>(inputStream)
|
||||
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
||||
val moshi = Moshi.Builder().build()
|
||||
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.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
@ -33,24 +32,10 @@ class AttendanceRepository @Inject constructor(
|
|||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
fun getAttendance(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||
|
@ -58,39 +43,19 @@ class AttendanceRepository @Inject constructor(
|
|||
},
|
||||
saveFetchResult = { old, new ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
||||
newAttendance.apply { if (notify) isNotified = false }
|
||||
}
|
||||
attendanceDb.insertAll(attendanceToAdd)
|
||||
attendanceDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
)
|
||||
|
||||
fun getAttendanceFromDatabase(
|
||||
semester: Semester,
|
||||
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 ->
|
||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||
Absent(
|
||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||
timeId = attendance.timeId
|
||||
)
|
||||
}
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.excuseForAbsence(items, reason)
|
||||
}, reason)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
|||
student: Student,
|
||||
semester: Semester,
|
||||
subjectId: Int,
|
||||
forceRefresh: Boolean,
|
||||
forceRefresh: Boolean
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
},
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
|
|
@ -28,28 +28,10 @@ class CompletedLessonsRepository @Inject constructor(
|
|||
|
||||
private val cacheKey = "completed"
|
||||
|
||||
fun getCompletedLessons(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
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
|
||||
)
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getCompletedLessons(start.monday, end.sunday)
|
||||
|
|
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
|||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
},
|
||||
query = {
|
||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||
|
|
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
|||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
examDb.loadAll(
|
||||
|
|
|
@ -37,12 +37,13 @@ class GradeRepository @Inject constructor(
|
|||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||
val isShouldBeRefreshed =
|
||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
|
@ -70,8 +71,8 @@ class GradeRepository @Inject constructor(
|
|||
newDetails: List<Grade>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||
?.date ?: student.registrationDate.toLocalDate()
|
||||
val notifyBreakDate =
|
||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
|
@ -88,7 +89,8 @@ class GradeRepository @Inject constructor(
|
|||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
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.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
|
|
|
@ -39,19 +39,9 @@ class GradeStatisticsRepository @Inject constructor(
|
|||
private val semesterCacheKey = "grade_stats_semester"
|
||||
private val pointsCacheKey = "grade_stats_points"
|
||||
|
||||
fun getGradesPartialStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = partialMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(partialCacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -86,19 +76,9 @@ class GradeStatisticsRepository @Inject constructor(
|
|||
}
|
||||
)
|
||||
|
||||
fun getGradesSemesterStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = semesterMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(semesterCacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -114,15 +94,13 @@ class GradeStatisticsRepository @Inject constructor(
|
|||
val itemsWithAverage = items.map { item ->
|
||||
item.copy().apply {
|
||||
val denominator = item.amounts.sum()
|
||||
average = if (denominator == 0) "" else {
|
||||
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
when (subjectName) {
|
||||
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
|
||||
studentId = semester.studentId,
|
||||
|
@ -131,9 +109,7 @@ class GradeStatisticsRepository @Inject constructor(
|
|||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||
studentGrade = 0
|
||||
).apply {
|
||||
average = itemsWithAverage.mapNotNull {
|
||||
it.average.replace(",", ".").toDoubleOrNull()
|
||||
}.average().let {
|
||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}).reversed()
|
||||
|
@ -142,17 +118,9 @@ class GradeStatisticsRepository @Inject constructor(
|
|||
}
|
||||
)
|
||||
|
||||
fun getGradesPointsStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = pointsMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
|
|
@ -30,19 +30,16 @@ class HomeworkRepository @Inject constructor(
|
|||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
homeworkDb.loadAll(
|
||||
|
@ -61,9 +58,8 @@ class HomeworkRepository @Inject constructor(
|
|||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||
|
||||
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||
homeworkDb.deleteAll(old uniqueSubtract new)
|
||||
homeworkDb.insertAll(homeWorkToSave)
|
||||
|
||||
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)
|
||||
|
||||
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 getLogFiles() = withContext(dispatchers.io) {
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
||||
?.filter { it.name.endsWith(".log") }!!
|
||||
suspend fun getLogFiles() = withContext(dispatchers.backgroundThread) {
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
|
||||
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 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) {
|
||||
lastModifiedTime = file.lastModified()
|
||||
chosenFile = file
|
||||
}
|
||||
}
|
||||
|
||||
chosenFile ?: throw FileNotFoundException("Log file not found")
|
||||
if (chosenFile == null) throw FileNotFoundException("Log file not found")
|
||||
chosenFile!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,17 +23,11 @@ class LuckyNumberRepository @Inject constructor(
|
|||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getLuckyNumber(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = {
|
||||
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||
},
|
||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||
saveFetchResult = { old, new ->
|
||||
if (new != old) {
|
||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
|
@ -47,11 +41,9 @@ class LuckyNumberRepository @Inject constructor(
|
|||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||
luckyNumberDb.load(student.studentId, now()).map {
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
|
||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) =
|
||||
luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
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.mapToEntities
|
||||
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.pojo.Folder
|
||||
import io.github.wulkanowy.sdk.pojo.SentMessage
|
||||
|
@ -27,9 +29,6 @@ import io.github.wulkanowy.utils.networkBoundResource
|
|||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
|
@ -43,7 +42,7 @@ class MessageRepository @Inject constructor(
|
|||
@ApplicationContext private val context: Context,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sharedPrefProvider: SharedPrefProvider,
|
||||
private val json: Json,
|
||||
private val moshi: Moshi,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
@ -52,18 +51,14 @@ class MessageRepository @Inject constructor(
|
|||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
student: Student, semester: Semester,
|
||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student, folder)
|
||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, student, folder)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = {
|
||||
|
@ -82,8 +77,7 @@ class MessageRepository @Inject constructor(
|
|||
)
|
||||
|
||||
private fun getMessagesWithReadByChange(
|
||||
old: List<Message>,
|
||||
new: List<Message>,
|
||||
old: List<Message>, new: List<Message>,
|
||||
setNotified: Boolean
|
||||
): List<Message> {
|
||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||
|
@ -102,9 +96,7 @@ class MessageRepository @Inject constructor(
|
|||
}
|
||||
|
||||
fun getMessage(
|
||||
student: Student,
|
||||
message: Message,
|
||||
markAsRead: Boolean = false,
|
||||
student: Student, message: Message, markAsRead: Boolean = false
|
||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||
shouldFetch = {
|
||||
checkNotNull(it, { "This message no longer exist!" })
|
||||
|
@ -143,10 +135,8 @@ class MessageRepository @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun sendMessage(
|
||||
student: Student,
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
student: Student, subject: String, content: String,
|
||||
recipients: List<Recipient>
|
||||
): SentMessage = sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
|
@ -169,9 +159,9 @@ class MessageRepository @Inject constructor(
|
|||
|
||||
var draftMessage: MessageDraft?
|
||||
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(
|
||||
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"
|
||||
|
||||
fun getDevices(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||
fetch = {
|
||||
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.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -27,19 +28,9 @@ class NoteRepository @Inject constructor(
|
|||
|
||||
private val cacheKey = "note"
|
||||
|
||||
fun getNotes(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
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 com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
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 io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.sdk.toLocalDate
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
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.utils.toTimestamp
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
@ -30,12 +28,16 @@ import javax.inject.Singleton
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Singleton
|
||||
class PreferencesRepository @Inject constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
private val sharedPref: SharedPreferences,
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
val gradeExpandMode: GradeExpandMode
|
||||
get() = GradeExpandMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_expand_grade_mode,
|
||||
R.string.pref_default_expand_grade_mode
|
||||
)
|
||||
)
|
||||
val isGradeExpandable: Boolean
|
||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||
|
||||
val showAllSubjectsOnStatisticsList: Boolean
|
||||
get() = getBoolean(
|
||||
|
@ -111,22 +108,6 @@ class PreferencesRepository @Inject constructor(
|
|||
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 isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
|
@ -195,20 +176,22 @@ class PreferencesRepository @Inject constructor(
|
|||
)
|
||||
|
||||
var lasSyncDate: LocalDateTime
|
||||
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||
.toLocalDateTime()
|
||||
get() = getLong(
|
||||
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()
|
||||
|
||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||
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 {
|
||||
putString(
|
||||
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
||||
json.encodeToString(value)
|
||||
dashboardItemsPositionAdapter.toJson(value)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -217,7 +200,6 @@ class PreferencesRepository @Inject constructor(
|
|||
.map { set ->
|
||||
set.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
}
|
||||
|
||||
|
@ -225,7 +207,6 @@ class PreferencesRepository @Inject constructor(
|
|||
get() = selectedDashboardTilesPreference.get()
|
||||
.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
set(value) {
|
||||
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()
|
||||
|
||||
var inAppReviewDate: LocalDate?
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||
?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||
.apply()
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
||||
|
||||
var isAppReviewDone: Boolean
|
||||
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) =
|
||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
|
||||
private fun getBoolean(id: Int, default: Boolean) =
|
||||
sharedPref.getBoolean(context.getString(id), default)
|
||||
|
||||
private companion object {
|
||||
|
||||
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.mappers.mapToEntities
|
||||
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.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
|
@ -17,34 +15,26 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class RecipientRepository @Inject constructor(
|
||||
private val recipientDb: RecipientDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val cacheKey = "recipient"
|
||||
|
||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
return if (cached.isEmpty() || isExpired) {
|
||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
||||
refreshRecipients(student, unit, role)
|
||||
|
||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
} else cached
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
||||
.mapToEntities(student.studentId)
|
||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
|||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||
}
|
||||
|
||||
suspend fun sendRecoverRequest(
|
||||
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
||||
return 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.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
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.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -28,15 +30,17 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||
|
||||
fun getSchoolAnnouncements(
|
||||
student: Student,
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(student.studentId)
|
||||
schoolAnnouncementDb.loadAll(
|
||||
student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
|
@ -53,11 +57,9 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||
}
|
||||
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<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.mappers.mapToEntity
|
||||
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.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
@ -16,26 +14,15 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class SchoolRepository @Inject constructor(
|
||||
private val schoolDb: SchoolDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student)
|
||||
)
|
||||
it == null || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
|
@ -50,7 +37,6 @@ class SchoolRepository @Inject constructor(
|
|||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class SemesterRepository @Inject constructor(
|
|||
student: Student,
|
||||
forceRefresh: Boolean = false,
|
||||
refreshOnNoCurrent: Boolean = false
|
||||
) = withContext(dispatchers.io) {
|
||||
) = withContext(dispatchers.backgroundThread) {
|
||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
|
||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||
|
@ -64,7 +64,7 @@ class SemesterRepository @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
withContext(dispatchers.io) {
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,8 @@ class StudentInfoRepository @Inject constructor(
|
|||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.withTransaction
|
||||
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.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
|
@ -27,8 +25,7 @@ class StudentRepository @Inject constructor(
|
|||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val appInfo: AppInfo,
|
||||
private val appDatabase: AppDatabase
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
|
@ -66,7 +63,7 @@ class StudentRepository @Inject constructor(
|
|||
.map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +74,7 @@ class StudentRepository @Inject constructor(
|
|||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
|
@ -88,40 +85,35 @@ class StudentRepository @Inject constructor(
|
|||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||
val students = studentsWithSemesters.map { it.student }
|
||||
.map {
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
password = withContext(dispatchers.io) {
|
||||
password = withContext(dispatchers.backgroundThread) {
|
||||
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)
|
||||
studentDb.insertAll(students)
|
||||
}
|
||||
return studentDb.insertAll(students)
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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.mappers.mapToEntities
|
||||
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.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
|
@ -17,24 +15,14 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val subjectDao: SubjectDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -43,8 +31,6 @@ class SubjectRepository @Inject constructor(
|
|||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
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.mappers.mapToEntities
|
||||
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.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
|
@ -17,24 +15,14 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class TeacherRepository @Inject constructor(
|
||||
private val teacherDb: TeacherDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -44,8 +32,6 @@ class TeacherRepository @Inject constructor(
|
|||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,23 +41,18 @@ class TimetableRepository @Inject constructor(
|
|||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
refreshAdditional: Boolean = false,
|
||||
notify: Boolean = false
|
||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional, headers) ->
|
||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||
|
||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||
|
||||
isNoData || forceRefresh || isExpired
|
||||
isNoData || forceRefresh || isShouldRefresh
|
||||
},
|
||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||
fetch = {
|
||||
|
@ -68,7 +63,7 @@ class TimetableRepository @Inject constructor(
|
|||
timetableFull.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { timetableOld, timetableNew ->
|
||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify)
|
||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
|
||||
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||
|
||||
|
@ -84,10 +79,8 @@ class TimetableRepository @Inject constructor(
|
|||
)
|
||||
|
||||
private fun getFullTimetableFromDatabase(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate
|
||||
): Flow<TimetableFull> {
|
||||
val timetableFlow = timetableDb.loadAll(
|
||||
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(
|
||||
student: Student,
|
||||
lessonsOld: List<Timetable>,
|
||||
lessonsNew: List<Timetable>,
|
||||
notify: Boolean
|
||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
||||
) {
|
||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||
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)
|
||||
|
|
|
@ -15,7 +15,6 @@ import dagger.multibindings.IntoSet
|
|||
import io.github.wulkanowy.services.sync.channels.Channel
|
||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||
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.NewExamChannel
|
||||
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.NewSchoolAnnouncementsChannel
|
||||
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.works.AttendanceSummaryWork
|
||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||
|
@ -169,12 +167,4 @@ abstract class ServicesModule {
|
|||
@Binds
|
||||
@IntoSet
|
||||
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
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
|
@ -10,13 +11,11 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||
import io.github.wulkanowy.utils.PendingIntentCompat
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
|
@ -33,15 +32,12 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||
@Inject
|
||||
lateinit var studentRepository: StudentRepository
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||
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_ID = "student_id"
|
||||
|
@ -71,10 +67,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||
|
||||
private fun prepareNotification(context: Context, intent: Intent) {
|
||||
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) {
|
||||
return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
showNotification(
|
||||
context, isPersistent, studentName,
|
||||
showNotification(context, notificationId, studentName,
|
||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||
context.getString(
|
||||
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||
"($room) $subject".removePrefix("()")
|
||||
),
|
||||
nextSubject?.let {
|
||||
context.getString(
|
||||
R.string.timetable_later,
|
||||
"($nextRoom) $nextSubject".removePrefix("()")
|
||||
)
|
||||
}
|
||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
context: Context,
|
||||
isPersistent: Boolean,
|
||||
studentName: String?,
|
||||
countDown: Long,
|
||||
timeout: Long,
|
||||
title: String,
|
||||
next: String?
|
||||
) {
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setWhen(countDown)
|
||||
.setOngoing(isPersistent)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
|
@ -134,14 +111,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||
it.setSummaryText(studentName)
|
||||
it.addLine(next)
|
||||
})
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
NOTIFICATION_ID,
|
||||
SplashActivity.getStartIntent(context, Destination.Timetable()),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.services.alarm
|
|||
import android.app.AlarmManager
|
||||
import android.app.AlarmManager.RTC_WAKEUP
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.STUDENT_ID
|
||||
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.PendingIntentCompat
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
|
@ -53,17 +53,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||
|
||||
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
||||
val studentId = student.studentId
|
||||
withContext(dispatchersProvider.io) {
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(
|
||||
range = upcomingTime..lesson.start,
|
||||
requestCode = getRequestCode(upcomingTime, studentId)
|
||||
)
|
||||
cancelScheduledTo(
|
||||
range = lesson.start..lesson.end,
|
||||
requestCode = getRequestCode(lesson.start, studentId)
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
)
|
||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||
|
||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||
}
|
||||
|
@ -72,31 +69,20 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||
|
||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||
if (now() in range) cancelNotification()
|
||||
|
||||
alarmManager.cancel(
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestCode,
|
||||
Intent(),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelNotification() =
|
||||
NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
|
||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||
return cancelScheduled(lessons, student)
|
||||
}
|
||||
|
||||
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
||||
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
||||
return
|
||||
}
|
||||
|
||||
withContext(dispatchersProvider.io) {
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.groupBy { it.date }
|
||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
||||
|
@ -110,26 +96,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||
time = getUpcomingLessonTime(index, active, lesson)
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||
time = lesson.start
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
time = lesson.end
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -157,20 +143,17 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
try {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, 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) {
|
||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||
val serviceInterval = preferencesRepository.servicesInterval
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||
.setInitialDelay(10, MINUTES)
|
||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build())
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
||||
fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
|
||||
fun startOneTimeSyncWorker(): Flow<WorkInfo> {
|
||||
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
|
@ -83,11 +77,7 @@ class SyncManager @Inject constructor(
|
|||
)
|
||||
.build()
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
"${SyncWorker::class.java.simpleName}_one_time",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
work
|
||||
)
|
||||
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
||||
|
||||
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.services.sync.channels.DebugChannel
|
||||
import io.github.wulkanowy.services.sync.works.Work
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.random.Random
|
||||
|
||||
@HiltWorker
|
||||
|
@ -34,14 +34,13 @@ class SyncWorker @AssistedInject constructor(
|
|||
private val semesterRepository: SemesterRepository,
|
||||
private val works: Set<@JvmSuppressWildcards Work>,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val dispatchersProvider: DispatchersProvider
|
||||
private val notificationManager: NotificationManagerCompat
|
||||
) : CoroutineWorker(appContext, workerParameters) {
|
||||
|
||||
override suspend fun doWork() = withContext(dispatchersProvider.io) {
|
||||
override suspend fun doWork() = coroutineScope {
|
||||
Timber.i("SyncWorker is starting")
|
||||
|
||||
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
|
||||
if (!studentRepository.isCurrentStudentSet()) return@coroutineScope Result.failure()
|
||||
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||
|
@ -51,12 +50,12 @@ class SyncWorker @AssistedInject constructor(
|
|||
Timber.i("${work::class.java.simpleName} is starting")
|
||||
work.doWork(student, semester)
|
||||
Timber.i("${work::class.java.simpleName} result: Success")
|
||||
preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
||||
null
|
||||
} else {
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
|
||||
else {
|
||||
Timber.e(e)
|
||||
e
|
||||
}
|
||||
|
@ -71,16 +70,13 @@ class SyncWorker @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
exceptions.isNotEmpty() -> Result.retry()
|
||||
else -> {
|
||||
preferencesRepository.lasSyncDate = LocalDateTime.now()
|
||||
Result.success()
|
||||
}
|
||||
else -> Result.success()
|
||||
}
|
||||
|
||||
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
||||
Timber.i("SyncWorker result: $result")
|
||||
|
||||
return@withContext 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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
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 lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notificationDataList = lines.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
|
||||
content = it,
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_CONFERENCE,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||
contentStringRes = R.plurals.conference_notify_new_items,
|
||||
summaryStringRes = R.plurals.conference_number_item,
|
||||
startMenu = MainView.Section.CONFERENCE,
|
||||
lines = lines
|
||||
)
|
||||
|
||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||
sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,38 @@
|
|||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
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 lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notificationDataList = lines.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
|
||||
content = it,
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_EXAM,
|
||||
icon = R.drawable.ic_main_exam,
|
||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||
contentStringRes = R.plurals.exam_notify_new_item_content,
|
||||
summaryStringRes = R.plurals.exam_number_item,
|
||||
startMenu = MainView.Section.EXAM,
|
||||
lines = lines
|
||||
)
|
||||
|
||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||
sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +1,66 @@
|
|||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items, 1),
|
||||
content = "${it.subject}: ${it.entry}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_DETAILS,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items,
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
|
||||
content = "${it.subject}: ${it.predictedGrade}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items_final, 1),
|
||||
content = "${it.subject}: ${it.finalGrade}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_FINAL,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_final,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
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 lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notificationDataList = lines.map {
|
||||
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),
|
||||
val notification = MultipleNotifications(
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
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.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
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) {
|
||||
val notificationData = NotificationData(
|
||||
title = context.getString(R.string.lucky_number_notify_new_item_title),
|
||||
content = context.getString(
|
||||
R.string.lucky_number_notify_new_item,
|
||||
item.luckyNumber.toString()
|
||||
),
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber)
|
||||
fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotification(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
|
||||
appNotificationManager.sendSingleNotification(
|
||||
notificationData = notificationData,
|
||||
notificationType = NotificationType.NEW_LUCKY_NUMBER,
|
||||
student = student
|
||||
)
|
||||
sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,33 @@
|
|||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.message_new_items, 1),
|
||||
content = "${it.sender}: ${it.subject}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
||||
)
|
||||
fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_MESSAGE,
|
||||
icon = R.drawable.ic_stat_message,
|
||||
titleStringRes = R.plurals.message_new_items,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
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.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||
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.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
val titleRes = when (NoteCategory.getByValue(it.categoryType)) {
|
||||
fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
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.NEUTRAL -> R.plurals.neutral_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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
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.getPlural
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
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) {
|
||||
val notificationDataList = items.map {
|
||||
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(
|
||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
intentToStart = SplashActivity.getStartIntent(
|
||||
context = context,
|
||||
destination = Destination.SchoolAnnouncement
|
||||
),
|
||||
title = context.getPlural(
|
||||
R.plurals.school_announcement_notify_new_item_title,
|
||||
items.size
|
||||
),
|
||||
content = context.getPlural(
|
||||
R.plurals.school_announcement_notify_new_items,
|
||||
items.size,
|
||||
items.size
|
||||
),
|
||||
notificationDataList = notificationDataList
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
)
|
||||
|
||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||
sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
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.NewAttendanceChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||
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.NewNotesChannel
|
||||
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(
|
||||
val group: String?,
|
||||
val channel: String,
|
||||
val icon: Int
|
||||
) {
|
||||
NEW_CONFERENCE(
|
||||
group = "new_conferences_group",
|
||||
channel = NewConferencesChannel.CHANNEL_ID,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
),
|
||||
NEW_EXAM(
|
||||
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
|
||||
)
|
||||
enum class NotificationType(val group: String, val channel: String) {
|
||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID),
|
||||
NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID),
|
||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||
}
|
||||
|
|
|
@ -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.Student
|
||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
|
||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceWork @Inject constructor(
|
||||
private val attendanceRepository: AttendanceRepository,
|
||||
private val newAttendanceNotification: NewAttendanceNotification,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) : Work {
|
||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
attendanceRepository.getAttendance(
|
||||
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
|
||||
})
|
||||
}
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.PreferencesRepository
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
|
@ -21,13 +22,13 @@ class HomeworkWork @Inject constructor(
|
|||
homeworkRepository.getHomework(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = now().nextOrSameSchoolDay,
|
||||
end = now().nextOrSameSchoolDay,
|
||||
start = now().monday,
|
||||
end = now().sunday,
|
||||
forceRefresh = true,
|
||||
notify = preferencesRepository.isNotificationsEnable
|
||||
).waitForResult()
|
||||
|
||||
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
|
||||
homeworkRepository.getHomeworkFromDatabase(semester, now().monday, now().sunday).first()
|
||||
.filter { !it.isNotified }.let {
|
||||
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.Student
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimetableWork @Inject constructor(
|
||||
private val timetableRepository: TimetableRepository,
|
||||
private val changeTimetableNotification: ChangeTimetableNotification,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
private val timetableRepository: TimetableRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
timetableRepository.getTimetable(
|
||||
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
|
||||
})
|
||||
}
|
||||
timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package io.github.wulkanowy.ui.base
|
||||
|
||||
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.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
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)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(
|
||||
|
@ -79,8 +83,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
|||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
startActivity(LoginActivity.getStartIntent(this))
|
||||
finishAffinity()
|
||||
startActivity(LoginActivity.getStartIntent(this)
|
||||
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -2,33 +2,32 @@ package io.github.wulkanowy.ui.base
|
|||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
||||
class BaseFragmentPagerAdapter(
|
||||
private val fragmentManager: FragmentManager,
|
||||
private val pagesCount: Int,
|
||||
lifecycle: Lifecycle,
|
||||
) : FragmentStateAdapter(fragmentManager, lifecycle), TabLayoutMediator.TabConfigurationStrategy {
|
||||
//TODO Use ViewPager2
|
||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
lateinit var itemFactory: (position: Int) -> Fragment
|
||||
|
||||
var titleFactory: (position: Int) -> String? = { "" }
|
||||
private val pages = mutableMapOf<Fragment, String?>()
|
||||
|
||||
var containerId = 0
|
||||
|
||||
fun getFragmentInstance(position: Int): Fragment? {
|
||||
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)
|
||||
|
||||
override fun getItemCount() = pagesCount
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
tab.text = titleFactory(position)
|
||||
fun addFragments(fragments: List<Fragment>) {
|
||||
fragments.forEach { pages[it] = null }
|
||||
}
|
||||
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
open class BasePresenter<T : BaseView>(
|
||||
protected val errorHandler: ErrorHandler,
|
||||
protected val studentRepository: StudentRepository
|
||||
) {
|
||||
private val job = SupervisorJob()
|
||||
) : CoroutineScope {
|
||||
|
||||
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
|
||||
|
||||
open fun onAttachView(view: T) {
|
||||
job = Job()
|
||||
this.view = view
|
||||
errorHandler.apply {
|
||||
showErrorMessage = view::showError
|
||||
|
@ -62,22 +64,22 @@ open class BasePresenter<T : BaseView>(
|
|||
}
|
||||
|
||||
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
||||
childrenJobs[individualJobTag]?.cancel()
|
||||
val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope)
|
||||
childrenJobs[individualJobTag] = job
|
||||
jobs[individualJobTag]?.cancel()
|
||||
val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
|
||||
jobs[individualJobTag] = job
|
||||
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
||||
return job
|
||||
}
|
||||
|
||||
fun cancelJobs(vararg names: String) {
|
||||
names.forEach {
|
||||
childrenJobs[it]?.cancel()
|
||||
jobs[it]?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
open fun onDetachView() {
|
||||
job.cancelChildren()
|
||||
errorHandler.clear()
|
||||
view = null
|
||||
job.cancel()
|
||||
errorHandler.clear()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import android.widget.Toast
|
|||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.isGone
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||
|
@ -25,6 +24,8 @@ import io.github.wulkanowy.utils.openEmailClient
|
|||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
|
@ -63,26 +64,26 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val errorStacktrace = error.stackTraceToString()
|
||||
val stringWriter = StringWriter().apply {
|
||||
error.printStackTrace(PrintWriter(this))
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
|
||||
errorDialogContent.text = stringWriter.toString()
|
||||
with(errorDialogHorizontalScroll) {
|
||||
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
||||
}
|
||||
errorDialogCopy.setOnClickListener {
|
||||
val clip = ClipData.newPlainText("Error details", errorStacktrace)
|
||||
val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
|
||||
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
||||
|
||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||
}
|
||||
errorDialogCancel.setOnClickListener { dismiss() }
|
||||
errorDialogReport.setOnClickListener {
|
||||
openConfirmDialog { openEmailClient(errorStacktrace) }
|
||||
openConfirmDialog { openEmailClient(stringWriter.toString()) }
|
||||
}
|
||||
errorDialogHumanizedMessage.text = resources.getString(error)
|
||||
errorDialogErrorMessage.text = error.localizedMessage
|
||||
errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
|
||||
errorDialogMessage.text = resources.getString(error)
|
||||
errorDialogReport.isEnabled = when (error) {
|
||||
is UnknownHostException,
|
||||
is InterruptedIOException,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import android.content.res.Resources
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
|
@ -10,7 +9,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
|||
import timber.log.Timber
|
||||
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 = { _, _ -> }
|
||||
|
||||
|
@ -26,7 +25,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||
}
|
||||
|
||||
protected open fun proceed(error: Throwable) {
|
||||
showErrorMessage(context.resources.getString(error), error)
|
||||
showErrorMessage(resources.getString(error), error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
|
|
|
@ -41,15 +41,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
|||
)
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity) =
|
||||
activity.packageManager
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||
return activity.packageManager
|
||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities
|
||||
.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme
|
||||
.let {
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme.let {
|
||||
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
||||
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
||||
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,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() {
|
||||
view?.run {
|
||||
updateData(
|
||||
listOfNotNull(
|
||||
updateData(listOfNotNull(
|
||||
versionRes,
|
||||
creatorsRes,
|
||||
feedbackRes,
|
||||
|
@ -94,8 +93,7 @@ class AboutPresenter @Inject constructor(
|
|||
homepageRes,
|
||||
licensesRes,
|
||||
privacyRes
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class LicensePresenter @Inject constructor(
|
|||
|
||||
private fun loadData() {
|
||||
flowWithResource {
|
||||
withContext(dispatchers.io) {
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
view?.appLibraries.orEmpty()
|
||||
}
|
||||
}.onEach {
|
||||
|
|
|
@ -121,14 +121,10 @@ class AccountDetailsFragment :
|
|||
}
|
||||
}
|
||||
|
||||
override fun popViewToMain() {
|
||||
override fun popView() {
|
||||
(requireActivity() as MainActivity).popView(2)
|
||||
}
|
||||
|
||||
override fun popViewToAccounts() {
|
||||
(requireActivity() as MainActivity).popView(1)
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class AccountDetailsPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popViewToMain()
|
||||
view?.popView()
|
||||
}.launch("switch")
|
||||
}
|
||||
|
||||
|
@ -152,14 +152,11 @@ class AccountDetailsPresenter @Inject constructor(
|
|||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters?.student?.isCurrent == true -> {
|
||||
studentWithSemesters!!.student.isCurrent -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
else -> {
|
||||
Timber.i("Logout result: Logout student")
|
||||
recreateMainView()
|
||||
}
|
||||
else -> Timber.i("Logout result: Logout student")
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
|
@ -168,11 +165,7 @@ class AccountDetailsPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
if (studentWithSemesters?.student?.isCurrent == true) {
|
||||
view?.popViewToMain()
|
||||
} else {
|
||||
view?.popViewToAccounts()
|
||||
}
|
||||
view?.popView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,7 @@ interface AccountDetailsView : BaseView {
|
|||
|
||||
fun showLogoutConfirmDialog()
|
||||
|
||||
fun popViewToMain()
|
||||
|
||||
fun popViewToAccounts()
|
||||
fun popView()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
|
|
|
@ -3,9 +3,12 @@ package io.github.wulkanowy.ui.modules.account.accountedit
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -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 {
|
||||
shape = GradientDrawable.OVAL
|
||||
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
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.github.wulkanowy.R
|
|||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.enums.SentExcuseStatus
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -36,7 +36,7 @@ class AttendanceAdapter @Inject constructor() :
|
|||
with(holder.binding) {
|
||||
attendanceItemNumber.text = item.number.toString()
|
||||
attendanceItemSubject.text = item.subject
|
||||
attendanceItemDescription.setText(item.descriptionRes)
|
||||
attendanceItemDescription.setText(item.description)
|
||||
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
|
||||
attendanceItemNumber.visibility = View.GONE
|
||||
attendanceItemExcuseInfo.visibility = View.GONE
|
||||
|
@ -46,7 +46,7 @@ class AttendanceAdapter @Inject constructor() :
|
|||
onExcuseCheckboxSelect(item, checked)
|
||||
}
|
||||
|
||||
when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it)}) {
|
||||
when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) {
|
||||
SentExcuseStatus.WAITING -> {
|
||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
||||
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue