1
0
Fork 1

Compare commits

..

No commits in common. "1.7.0" and "1.4.4" have entirely different histories.
1.7.0 ... 1.4.4

351 changed files with 6198 additions and 25347 deletions

View file

@ -1,12 +0,0 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=4
[*.json]
indent_size=2
[*.{kt,kts}]
disabled_rules=import-ordering,no-wildcard-imports

View file

@ -1,4 +1,4 @@
name: Deploy release
name: Deploy to app stores
on:
release:
@ -7,17 +7,16 @@ on:
jobs:
deploy-google-play:
name: Google Play
name: Deploy to google play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
@ -42,17 +41,16 @@ jobs:
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery:
name: AppGallery
name: Deploy to AppGallery
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-gallery
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches

View file

@ -1,4 +1,4 @@
name: Deploy DEV
name: Deploy to app tests
on:
push:
@ -18,12 +18,11 @@ jobs:
timeout-minutes: 10
environment: app-center
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
@ -67,7 +66,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assembleFdroidDebug --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
@ -88,12 +87,11 @@ jobs:
environment: app-distribution
if: github.event_name != 'pull_request_target'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
@ -133,7 +131,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
path: app/build/outputs/apk/play/debug/app-play-debug.apk

View file

@ -8,20 +8,18 @@ on:
branches: [ master, develop ]
jobs:
tests-fdroid:
name: F-Droid
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
@ -31,58 +29,6 @@ jobs:
run: |
./gradlew testFdroidDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit
tests-play:
name: Play
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testPlayDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit
tests-hms:
name: HMS
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testHmsDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v1
with:
flags: unit

View file

@ -2,6 +2,14 @@
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
@ -118,6 +126,13 @@
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Wulkanowy
Copyright 2021 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -51,7 +51,7 @@ Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGal
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Sie können auch eine [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) die beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
Sie können auch ein [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) das beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
## Gebaut mit

View file

@ -15,16 +15,15 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
namespace 'io.github.wulkanowy'
compileSdkVersion 32
compileSdkVersion 31
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 32
versionCode 109
versionName "1.7.0"
targetSdkVersion 31
versionCode 102
versionName "1.4.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -43,7 +42,6 @@ android {
}
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
if (System.env.SET_BUILD_TIMESTAMP) {
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
@ -75,8 +73,6 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
}
debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
@ -100,8 +96,6 @@ android {
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
]
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "\"${System.getenv("DASHBOARD_TILE_AD_ID") ?: "ca-app-pub-3940256099942544/6300978111"}\""
}
fdroid {
@ -126,8 +120,6 @@ android {
testOptions.unitTests {
includeAndroidResources = true
// workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750
all { jvmArgs '-noverify' }
}
compileOptions {
@ -138,14 +130,12 @@ android {
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
}
packagingOptions {
resources {
excludes += ['META-INF/library_release.kotlin_module',
'META-INF/library-core_release.kotlin_module']
}
exclude 'META-INF/library_release.kotlin_module'
exclude 'META-INF/library-core_release.kotlin_module'
}
aboutLibraries {
@ -159,10 +149,8 @@ kapt {
play {
defaultToAppBundles = false
track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.05d
updatePriority = 5
track = 'beta'
updatePriority = 4
enabled.set(false)
}
@ -179,34 +167,34 @@ huaweiPublish {
ext {
work_manager = "2.7.1"
android_hilt = "1.0.0"
room = "2.4.3"
room = "2.3.0"
chucker = "3.5.2"
mockk = "1.12.5"
coroutines = "1.6.4"
mockk = "1.12.1"
coroutines = "1.5.2"
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.7.0"
implementation "io.github.wulkanowy:sdk:1.4.4"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.8.0"
implementation 'androidx.core:core-splashscreen:1.0.0'
implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.appcompat:appcompat:1.5.0"
implementation "androidx.fragment:fragment-ktx:1.5.2"
implementation "androidx.annotation:annotation:1.4.0"
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"
implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.6.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
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.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.2.0'
@ -214,7 +202,7 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
@ -230,40 +218,39 @@ dependencies {
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.10.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
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.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:2.2.0"
implementation "io.coil-kt:coil:1.4.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.8.0'
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
playImplementation platform('com.google.firebase:firebase-bom:30.3.2')
playImplementation platform('com.google.firebase:firebase-bom:29.0.2')
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.3'
playImplementation 'com.google.android.play:core:1.10.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:21.1.0'
playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.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.haroldadmin:WhatTheStack:1.0.0-alpha04'
testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.8.1'
testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0"

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

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

View file

@ -1,55 +1,20 @@
{
"agcgw":{
"backurl": "connect-dre.hispace.hicloud.com",
"url": "connect-dre.dbankcloud.cn",
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
},
"agcgw_all": {
"CN": "connect-drcn.dbankcloud.cn",
"CN_back": "connect-drcn.hispace.hicloud.com",
"DE": "connect-dre.dbankcloud.cn",
"DE_back": "connect-dre.hispace.hicloud.com",
"RU": "connect-drru.hispace.dbankcloud.ru",
"RU_back": "connect-drru.hispace.dbankcloud.cn",
"SG": "connect-dra.dbankcloud.cn",
"SG_back": "connect-dra.hispace.hicloud.com"
},
"websocketgw_all": {
"CN": "connect-ws-drcn.hispace.dbankcloud.cn",
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
"backurl":"connect-dre.dbankcloud.cn",
"url":"connect-dre.hispace.hicloud.com"
},
"client":{
"cp_id":"890048000024105546",
"product_id": "736430079244736562",
"client_id": "514530959291319360",
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
"project_id": "736430079244736562",
"app_id": "106552551",
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
"package_name": "io.github.wulkanowy.dev"
},
"oauth_client": {
"client_id": "106552551",
"client_type": 1
},
"app_info": {
"app_id": "106552551",
"package_name": "io.github.wulkanowy.dev"
"product_id":"",
"client_id":"",
"client_secret":"",
"app_id":"101440411",
"package_name":"io.github.wulkanowy.dev",
"api_key":""
},
"service":{
"analytics":{
"collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"resource_id":"p1",
"channel_id":""
},
@ -57,36 +22,12 @@
"url":"https://search-dre.cloud.huawei.com"
},
"cloudstorage":{
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu",
"storage_url_de": "https://ops-dre.agcstorage.link",
"storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn",
"storage_url_sg": "https://ops-dra.agcstorage.link",
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
"storage_url":"https://ops-dre.agcstorage.link"
},
"ml":{
"mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
}
},
"region":"DE",
"configuration_version": "3.0",
"appInfos": [
{
"package_name": "io.github.wulkanowy.dev",
"client": {
"app_id": "106552551"
},
"app_info": {
"package_name": "io.github.wulkanowy.dev",
"app_id": "106552551"
},
"oauth_client": {
"client_type": 1,
"client_id": "106552551"
}
}
]
"configuration_version":"1.0"
}

View file

@ -1,28 +0,0 @@
package io.github.wulkanowy.utils
import android.content.Context
import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
throw IllegalStateException("Can't get ad banner (F-droid)")
}
}
data class AdBanner(val view: View)

View file

@ -1,28 +0,0 @@
package io.github.wulkanowy.utils
import android.content.Context
import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
throw IllegalStateException("Can't get ad banner (HMS)")
}
}
data class AdBanner(val view: View)

View file

@ -7,7 +7,6 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
@Suppress("UNUSED_PARAMETER", "unused")
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context
) {

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" />
@ -113,6 +114,7 @@
</intent-filter>
</service>
<receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true"

View file

@ -1,7 +1,10 @@
package io.github.wulkanowy
import android.app.Application
import android.util.Log.*
import android.util.Log.DEBUG
import android.util.Log.INFO
import android.util.Log.VERBOSE
import android.webkit.WebView
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver
@ -9,7 +12,12 @@ import dagger.hilt.android.HiltAndroidApp
import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.DebugLogTree
import timber.log.Timber
import javax.inject.Inject
@ -31,15 +39,12 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject
lateinit var analyticsHelper: AnalyticsHelper
@Inject
lateinit var adsHelper: AdsHelper
override fun onCreate() {
super.onCreate()
initializeAppLanguage()
themeManager.applyDefaultTheme()
adsHelper.initialize()
initLogging()
fixWebViewLocale()
}
private fun initLogging() {
@ -71,6 +76,15 @@ class WulkanowyApp : Application(), Configuration.Provider {
}
}
private fun fixWebViewLocale() {
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
try {
WebView(this).destroy()
} catch (e: Throwable) {
//Ignore exceptions
}
}
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)

View file

@ -19,6 +19,7 @@ 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
@ -109,6 +110,7 @@ internal class DataModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
@Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
@ -195,7 +197,7 @@ internal class DataModule {
@Singleton
@Provides
fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao
@Singleton
@Provides

View file

@ -1,173 +1,23 @@
package io.github.wulkanowy.data
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
sealed class Resource<T> {
open class Loading<T> : Resource<T>()
data class Intermediate<T>(val data: T) : Loading<T>()
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val error: Throwable) : Resource<T>()
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
val <T> Resource<T>.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> null
is Resource.Error -> null
fun <T> error(error: Throwable?, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, error)
}
val <T> Resource<T>.errorOrNull: Throwable?
get() = when (this) {
is Resource.Error -> this.error
else -> null
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
fun <T> resourceFlow(block: suspend () -> T) = flow {
emit(Resource.Loading())
emit(Resource.Success(block()))
}.catch { emit(Resource.Error(it)) }
fun <T> flatResourceFlow(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.Loading())
emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading })
}.catch { emit(Resource.Error(it)) }
fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
is Resource.Success -> Resource.Success(block(this.data))
is Resource.Intermediate -> Resource.Intermediate(block(this.data))
is Resource.Loading -> Resource.Loading()
is Resource.Error -> Resource.Error(this.error)
}
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) {
is Resource.Loading -> "started"
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Error -> "exception occurred: ${it.error}"
}
Timber.i("$name: $description")
}
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
it.mapData(block)
}
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
when (it) {
is Resource.Success -> block(it.data)
is Resource.Intermediate -> block(it.data)
is Resource.Error,
is Resource.Loading -> Unit
}
}
fun <T> Flow<Resource<T>>.onResourceLoading(block: suspend () -> Unit) = onEach {
if (it is Resource.Loading) {
block()
}
}
fun <T> Flow<Resource<T>>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Intermediate) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Success) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
if (it is Resource.Error) {
block(it.error)
}
}
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
if (it !is Resource.Loading) {
block()
}
}
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loading }.first()
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
inline fun <ResultType, RequestType> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (ResultType) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline filterResult: (ResultType) -> ResultType = { it }
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val filteredResult = filterResult(data)
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
emit(Resource.Intermediate(filteredResult))
}
try {
val newData = fetch(data)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(filterResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
}
} else {
query().map { Resource.Success(filterResult(it)) }
})
}
@JvmName("networkBoundResourceWithMap")
inline fun <ResultType, RequestType, T> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (T) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline mapResult: (ResultType) -> T
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val mappedResult = mapResult(data)
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
emit(Resource.Intermediate(mappedResult))
}
try {
val newData = fetch(data)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
}
} else {
query().map { Resource.Success(mapResult(it)) }
})
enum class Status {
LOADING,
SUCCESS,
ERROR
}

View file

@ -1,11 +1,114 @@
package io.github.wulkanowy.data.db
import android.content.Context
import androidx.room.*
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.migrations.*
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.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
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
import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
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.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
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
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration18
import io.github.wulkanowy.data.db.migrations.Migration19
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration20
import io.github.wulkanowy.data.db.migrations.Migration21
import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
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.Migration44
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@ -30,7 +133,7 @@ import javax.inject.Singleton
Subject::class,
LuckyNumber::class,
CompletedLesson::class,
Mailbox::class,
ReportingUnit::class,
Recipient::class,
MobileDevice::class,
Teacher::class,
@ -43,11 +146,6 @@ import javax.inject.Singleton
Notification::class,
AdminMessage::class
],
autoMigrations = [
AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
)
@ -55,7 +153,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 51
const val VERSION_SCHEMA = 44
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@ -100,11 +198,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration41(sharedPrefProvider),
Migration42(),
Migration43(),
Migration44(),
Migration46(),
Migration49(),
Migration50(),
Migration51(),
Migration44()
)
fun newInstance(
@ -155,7 +249,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val completedLessonsDao: CompletedLessonsDao
abstract val mailboxDao: MailboxDao
abstract val reportingUnitDao: ReportingUnitDao
abstract val recipientDao: RecipientDao

View file

@ -1,36 +1,40 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.*
import java.util.*
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Month
import java.time.ZoneOffset
import java.util.*
import java.util.Date
class Converters {
private val json = Json
@TypeConverter
fun timestampToLocalDate(value: Long?): LocalDate? =
value?.let(::Date)?.toInstant()?.atZone(ZoneOffset.UTC)?.toLocalDate()
fun timestampToDate(value: Long?): LocalDate? = value?.run {
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate()
}
@TypeConverter
fun dateToTimestamp(date: LocalDate?): Long? = date?.toTimestamp()
fun dateToTimestamp(date: LocalDate?): Long? {
return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
}
@TypeConverter
fun instantToTimestamp(instant: Instant?): Long? = instant?.toEpochMilli()
fun timestampToTime(value: Long?): LocalDateTime? = value?.let {
LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC)
}
@TypeConverter
fun timestampToInstant(timestamp: Long?): Instant? = timestamp?.let(Instant::ofEpochMilli)
fun timeToTimestamp(date: LocalDateTime?): Long? {
return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
}
@TypeConverter
fun monthToInt(month: Month?) = month?.value
@ -61,11 +65,4 @@ class Converters {
emptyList() // handle errors from old gson Pair serialized data
}
}
@TypeConverter
fun destinationToString(destination: Destination) = json.encodeToString(destination)
@TypeConverter
fun stringToDestination(destination: String): Destination = json.decodeFromString(destination)
}

View file

@ -4,7 +4,7 @@ import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Conference
import kotlinx.coroutines.flow.Flow
import java.time.Instant
import java.time.LocalDateTime
import javax.inject.Singleton
@Dao
@ -12,5 +12,5 @@ import javax.inject.Singleton
interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int, startDate: Instant): Flow<List<Conference>>
fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>>
}

View file

@ -1,17 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Mailbox
import javax.inject.Singleton
@Singleton
@Dao
interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ")
suspend fun loadAll(userLoginId: Int): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId AND studentName = :studentName ")
suspend fun load(userLoginId: Int, studentName: String): Mailbox?
}

View file

@ -11,9 +11,9 @@ import kotlinx.coroutines.flow.Flow
interface MessagesDao : BaseDao<Message> {
@Transaction
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>
}

View file

@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface MobileDeviceDao : BaseDao<MobileDevice> {
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
@Query("SELECT * FROM MobileDevices WHERE student_id = :userLoginId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
}

View file

@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient
import javax.inject.Singleton
@ -10,6 +9,6 @@ import javax.inject.Singleton
@Dao
interface RecipientDao : BaseDao<Recipient> {
@Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey")
suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List<Recipient>
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role")
suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient>
}

View file

@ -0,0 +1,17 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.ReportingUnit
import javax.inject.Singleton
@Singleton
@Dao
interface ReportingUnitDao : BaseDao<ReportingUnit> {
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId")
suspend fun load(studentId: Int): List<ReportingUnit>
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId AND real_id = :unitId")
suspend fun loadOne(studentId: Int, unitId: Int): ReportingUnit?
}

View file

@ -10,6 +10,6 @@ import javax.inject.Singleton
@Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId ORDER BY date DESC")
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
}

View file

@ -1,7 +1,12 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.ABORT
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@ -33,10 +38,6 @@ abstract class StudentDao {
@Query("SELECT * FROM Students")
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
@Transaction
@Query("SELECT * FROM Students WHERE id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters?
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long)

View file

@ -5,7 +5,6 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
import java.util.UUID
import javax.inject.Singleton
@Dao
@ -13,13 +12,5 @@ import javax.inject.Singleton
interface TimetableAdditionalDao : BaseDao<TimetableAdditional> {
@Query("SELECT * FROM TimetableAdditional 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<TimetableAdditional>>
@Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId")
suspend fun deleteAllByRepeatId(repeatId: UUID)
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<TimetableAdditional>>
}

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDateTime
@Entity(tableName = "Conferences")
data class Conference(
@ -27,7 +27,7 @@ data class Conference(
@ColumnInfo(name = "conference_id")
val conferenceId: Int,
val date: Instant,
val date: LocalDateTime
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -24,8 +24,5 @@ data class GradeSemesterStatistics(
var id: Long = 0
@Transient
var classAverage: String = ""
@Transient
var studentAverage: String = ""
var average: String = ""
}

View file

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.time.Instant
import java.time.LocalDateTime
@Entity(tableName = "GradesSummary")
data class GradeSummary(
@ -45,8 +45,8 @@ data class GradeSummary(
var isFinalGradeNotified: Boolean = true
@ColumnInfo(name = "predicted_grade_last_change")
var predictedGradeLastChange: Instant = Instant.now()
var predictedGradeLastChange: LocalDateTime = LocalDateTime.now()
@ColumnInfo(name = "final_grade_last_change")
var finalGradeLastChange: Instant = Instant.now()
var finalGradeLastChange: LocalDateTime = LocalDateTime.now()
}

View file

@ -1,25 +0,0 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Mailboxes")
data class Mailbox(
@PrimaryKey
val globalKey: String,
val fullName: String,
val userName: String,
val userLoginId: Int,
val studentName: String,
val schoolNameShort: String,
val type: MailboxType,
)
enum class MailboxType {
STUDENT,
PARENT,
GUARDIAN,
EMPLOYEE,
UNKNOWN,
}

View file

@ -4,31 +4,40 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDateTime
@Entity(tableName = "Messages")
data class Message(
@ColumnInfo(name = "message_global_key")
val messageGlobalKey: String,
@ColumnInfo(name = "student_id")
val studentId: Long,
@ColumnInfo(name = "mailbox_key")
val mailboxKey: String,
@ColumnInfo(name = "real_id")
val realId: Int,
@ColumnInfo(name = "message_id")
val messageId: Int,
val correspondents: String,
@ColumnInfo(name = "sender_name")
val sender: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "recipient_name")
val recipient: String,
val subject: String,
val date: Instant,
val date: LocalDateTime,
@ColumnInfo(name = "folder_id")
val folderId: Int,
var unread: Boolean,
val removed: Boolean,
@ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean
) : Serializable {
@ -39,7 +48,11 @@ data class Message(
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = ""
var sender: String? = null
var recipients: String? = null
}

View file

@ -12,8 +12,11 @@ data class MessageAttachment(
@ColumnInfo(name = "real_id")
val realId: Int,
@ColumnInfo(name = "message_global_key")
val messageGlobalKey: String,
@ColumnInfo(name = "message_id")
val messageId: Int,
@ColumnInfo(name = "one_drive_id")
val oneDriveId: String,
@ColumnInfo(name = "url")
val url: String,

View file

@ -7,6 +7,6 @@ data class MessageWithAttachment(
@Embedded
val message: Message,
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
@Relation(parentColumn = "message_id", entityColumn = "message_id")
val attachments: List<MessageAttachment>
)

View file

@ -4,12 +4,12 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDateTime
@Entity(tableName = "MobileDevices")
data class MobileDevice(
@ColumnInfo(name = "user_login_id")
@ColumnInfo(name = "student_id")
val userLoginId: Int,
@ColumnInfo(name = "device_id")
@ -17,7 +17,7 @@ data class MobileDevice(
val name: String,
val date: Instant,
val date: LocalDateTime
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -4,8 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
import java.time.Instant
import java.time.LocalDateTime
@Entity(tableName = "Notifications")
data class Notification(
@ -19,10 +18,7 @@ data class Notification(
val type: NotificationType,
@ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}")
val destination: Destination,
val date: Instant,
val date: LocalDateTime,
val data: String? = null
) {

View file

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@ -7,16 +8,32 @@ import java.io.Serializable
@kotlinx.serialization.Serializable
@Entity(tableName = "Recipients")
data class Recipient(
val mailboxGlobalKey: String,
val studentMailboxGlobalKey: String,
val fullName: String,
val userName: String,
val schoolShortName: String,
val type: MailboxType,
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "real_id")
val realId: String,
val name: String,
@ColumnInfo(name = "real_name")
val realName: String,
@ColumnInfo(name = "login_id")
val loginId: Int,
@ColumnInfo(name = "unit_id")
val unitId: Int,
val role: Int,
val hash: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
override fun toString() = userName
override fun toString() = name
}

View file

@ -0,0 +1,32 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "ReportingUnits")
data class ReportingUnit(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "real_id")
val unitId: Int,
@ColumnInfo(name = "short")
val shortName: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "sender_name")
val senderName: String,
val roles: List<Int>
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View file

@ -9,8 +9,8 @@ import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement(
@ColumnInfo(name = "user_login_id")
val userLoginId: Int,
@ColumnInfo(name = "student_id")
val studentId: Int,
val date: LocalDate,

View file

@ -7,12 +7,7 @@ import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDate
@Entity(
tableName = "Semesters", indices = [Index(
value = ["student_id", "diary_id", "kindergarten_diary_id", "semester_id"],
unique = true
)]
)
@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)])
data class Semester(
@ColumnInfo(name = "student_id")
@ -21,9 +16,6 @@ data class Semester(
@ColumnInfo(name = "diary_id")
val diaryId: Int,
@ColumnInfo(name = "kindergarten_diary_id", defaultValue = "0")
val kindergartenDiaryId: Int,
@ColumnInfo(name = "diary_name")
val diaryName: String,
@ -50,6 +42,7 @@ data class Semester(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_current")
var current: Boolean = false
}

View file

@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDateTime
@Entity(
tableName = "Students",
@ -74,7 +74,7 @@ data class Student(
val isCurrent: Boolean,
@ColumnInfo(name = "registration_date")
val registrationDate: Instant,
val registrationDate: LocalDateTime
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -4,8 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
@Entity(tableName = "Timetable")
data class Timetable(
@ -18,9 +18,9 @@ data class Timetable(
val number: Int,
val start: Instant,
val start: LocalDateTime,
val end: Instant,
val end: LocalDateTime,
val date: LocalDate,

View file

@ -4,9 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDate
import java.util.*
import java.time.LocalDateTime
@Entity(tableName = "TimetableAdditional")
data class TimetableAdditional(
@ -17,9 +16,9 @@ data class TimetableAdditional(
@ColumnInfo(name = "diary_id")
val diaryId: Int,
val start: Instant,
val start: LocalDateTime,
val end: Instant,
val end: LocalDateTime,
val date: LocalDate,
@ -28,10 +27,4 @@ data class TimetableAdditional(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "repeat_id", defaultValue = "NULL")
var repeatId: UUID? = null
@ColumnInfo(name = "is_added_by_user", defaultValue = "0")
var isAddedByUser: Boolean = false
}

View file

@ -43,14 +43,12 @@ class Migration12 : Migration(11, 12) {
private fun getStudentsIds(database: SupportSQLiteDatabase): List<Int> {
val students = mutableListOf<Int>()
database.query("SELECT student_id FROM Students").use {
if (it.moveToFirst()) {
val studentsCursor = database.query("SELECT student_id FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(it.getInt(0))
} while (it.moveToNext())
students.add(studentsCursor.getInt(0))
} while (studentsCursor.moveToNext())
}
}
return students
}

View file

@ -25,14 +25,12 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val students = mutableListOf<Pair<Int, String>>()
database.query("SELECT id, school_name FROM Students").use {
if (it.moveToFirst()) {
val studentsCursor = database.query("SELECT id, school_name FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(it.getInt(0) to it.getString(1))
} while (it.moveToNext())
students.add(studentsCursor.getInt(0) to studentsCursor.getString(1))
} while (studentsCursor.moveToNext())
}
}
return students
}
@ -44,14 +42,12 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List<Pair<Int, Int>> {
val students = mutableListOf<Pair<Int, Int>>()
database.query("SELECT student_id, class_id FROM Students").use {
if (it.moveToFirst()) {
val studentsCursor = database.query("SELECT student_id, class_id FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(it.getInt(0) to it.getInt(1))
} while (it.moveToNext())
students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1))
} while (studentsCursor.moveToNext())
}
}
return students
}

View file

@ -22,27 +22,23 @@ class Migration27 : Migration(26, 27) {
private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList<Triple<Long, Int, String>> {
val students = mutableListOf<Triple<Long, Int, String>>()
database.query("SELECT id, user_login_id, student_name FROM Students").use {
if (it.moveToFirst()) {
val studentsCursor = database.query("SELECT id, user_login_id, student_name FROM Students")
if (studentsCursor.moveToFirst()) {
do {
students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2)))
} while (it.moveToNext())
students.add(Triple(studentsCursor.getLong(0), studentsCursor.getInt(1), studentsCursor.getString(2)))
} while (studentsCursor.moveToNext())
}
}
return students
}
private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val units = mutableListOf<Pair<Int, String>>()
database.query("SELECT sender_id, sender_name FROM ReportingUnits").use {
if (it.moveToFirst()) {
val unitsCursor = database.query("SELECT sender_id, sender_name FROM ReportingUnits")
if (unitsCursor.moveToFirst()) {
do {
units.add(it.getInt(0) to it.getString(1))
} while (it.moveToNext())
units.add(unitsCursor.getInt(0) to unitsCursor.getString(1))
} while (unitsCursor.moveToNext())
}
}
return units
}

View file

@ -10,17 +10,15 @@ class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
database.query("SELECT * FROM Students").use {
while (it.moveToNext()) {
val studentId = it.getLongOrNull(0)
val studentsCursor = database.query("SELECT * FROM Students")
while (studentsCursor.moveToNext()) {
val studentId = studentsCursor.getLongOrNull(0)
database.execSQL(
"""
UPDATE Students
"""UPDATE Students
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
WHERE id = $studentId
"""
WHERE id = $studentId"""
)
}
}
}
}

View file

@ -1,102 +0,0 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.time.Instant
import java.time.ZoneId
import java.time.ZoneOffset
class Migration46 : Migration(45, 46) {
override fun migrate(database: SupportSQLiteDatabase) {
migrateConferences(database)
migrateMessages(database)
migrateMobileDevices(database)
migrateNotifications(database)
migrateTimetable(database)
migrateTimetableAdditional(database)
}
private fun migrateConferences(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Conferences").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMessages(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Messages").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMobileDevices(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM MobileDevices").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateNotifications(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Notifications").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateTimetable(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Timetable").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM TimetableAdditional").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun Long.timestampLocalToUTC(): Long = Instant.ofEpochMilli(this)
.atZone(ZoneOffset.UTC)
.withZoneSameLocal(ZoneId.of("Europe/Warsaw"))
.withZoneSameInstant(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
}

View file

@ -1,23 +0,0 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration49 : Migration(48, 49) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`user_login_id` INTEGER NOT NULL,
`date` INTEGER NOT NULL,
`subject` TEXT NOT NULL,
`content` TEXT NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL)
""".trimIndent()
)
}
}

View file

@ -1,21 +0,0 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration50 : Migration(49, 50) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MobileDevices")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MobileDevices` (
`user_login_id` INTEGER NOT NULL,
`device_id` INTEGER NOT NULL,
`name` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)
""".trimIndent()
)
}
}

View file

@ -1,88 +0,0 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration51 : Migration(50, 51) {
override fun migrate(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
recreateMessageAttachmentsTable(database)
recreateRecipientsTable(database)
deleteReportingUnitTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`userLoginId` INTEGER NOT NULL,
`studentName` TEXT NOT NULL,
`schoolNameShort` TEXT NOT NULL,
`type` TEXT NOT NULL,
PRIMARY KEY(`globalKey`)
)""".trimIndent()
)
}
private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`message_global_key` TEXT NOT NULL,
`mailbox_key` TEXT NOT NULL,
`message_id` INTEGER NOT NULL,
`correspondents` TEXT NOT NULL,
`subject` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`folder_id` INTEGER NOT NULL,
`unread` INTEGER NOT NULL,
`has_attachments` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`sender` TEXT, `recipients` TEXT
)""".trimIndent()
)
}
private fun recreateMessageAttachmentsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MessageAttachments")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MessageAttachments` (
`real_id` INTEGER NOT NULL,
`message_global_key` TEXT NOT NULL,
`url` TEXT NOT NULL,
`filename` TEXT NOT NULL,
PRIMARY KEY(`real_id`)
)""".trimIndent()
)
}
private fun recreateRecipientsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Recipients")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Recipients` (
`mailboxGlobalKey` TEXT NOT NULL,
`studentMailboxGlobalKey` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`schoolShortName` TEXT NOT NULL,
`type` TEXT NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
)""".trimIndent()
)
}
private fun deleteReportingUnitTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS ReportingUnits")
}
}

View file

@ -2,8 +2,7 @@ package io.github.wulkanowy.data.enums
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date"),
AVERAGE("average");
DATE("date");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC

View file

@ -10,7 +10,7 @@ fun List<SdkConference>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId,
agenda = it.agenda,
conferenceId = it.id,
date = it.dateZoned.toInstant(),
date = it.date,
presentOnConference = it.presentOnConference,
subject = it.subject,
title = it.title

View file

@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement(
userLoginId = student.userLoginId,
studentId = student.studentId,
date = it.date,
subject = it.subject,
content = it.content,

View file

@ -1,18 +0,0 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.Mailbox as SdkMailbox
fun List<SdkMailbox>.mapToEntities(student: Student) = map {
Mailbox(
globalKey = it.globalKey,
fullName = it.fullName,
userName = it.userName,
userLoginId = student.userLoginId,
studentName = it.studentName,
schoolNameShort = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name),
)
}

View file

@ -1,31 +1,40 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.sdk.pojo.MailboxType
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import java.time.LocalDateTime
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkMessage>.mapToEntities(mailbox: Mailbox) = map {
fun List<SdkMessage>.mapToEntities(student: Student) = map {
Message(
messageGlobalKey = it.globalKey,
mailboxKey = mailbox.globalKey,
messageId = it.id,
correspondents = it.correspondents,
studentId = student.id,
realId = it.id ?: 0,
messageId = it.messageId ?: 0,
sender = it.sender?.name.orEmpty(),
senderId = it.sender?.loginId ?: 0,
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
subject = it.subject.trim(),
date = it.dateZoned.toInstant(),
date = it.date ?: LocalDateTime.now(),
folderId = it.folderId,
unread = it.unread,
unread = it.unread ?: false,
removed = it.removed,
hasAttachments = it.hasAttachments
).apply {
content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
}
}
fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
fun List<SdkMessageAttachment>.mapToEntities() = map {
MessageAttachment(
messageGlobalKey = messageGlobalKey,
realId = it.url.hashCode(),
realId = it.id,
messageId = it.messageId,
oneDriveId = it.oneDriveId,
url = it.url,
filename = it.filename
)
@ -33,11 +42,12 @@ fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
fun List<Recipient>.mapFromEntities() = map {
SdkRecipient(
fullName = it.fullName,
userName = it.userName,
studentName = it.userName,
mailboxGlobalKey = it.mailboxGlobalKey,
schoolNameShort = it.schoolShortName,
type = MailboxType.valueOf(it.type.name),
id = it.realId,
name = it.realName,
loginId = it.loginId,
reportingUnitId = it.unitId,
role = it.role,
hash = it.hash,
shortName = it.name
)
}

View file

@ -1,15 +1,15 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
fun List<SdkDevice>.mapToEntities(student: Student) = map {
fun List<SdkDevice>.mapToEntities(semester: Semester) = map {
MobileDevice(
userLoginId = student.userLoginId,
date = it.createDateZoned.toInstant(),
userLoginId = semester.studentId,
date = it.createDate,
deviceId = it.id,
name = it.name
)

View file

@ -1,16 +1,17 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkRecipient>.mapToEntities(studentMailboxGlobalKey: String) = map {
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map {
Recipient(
mailboxGlobalKey = it.mailboxGlobalKey,
fullName = it.fullName,
userName = it.userName,
studentMailboxGlobalKey = studentMailboxGlobalKey,
schoolShortName = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name),
studentId = userLoginId,
realId = it.id,
realName = it.name,
name = it.shortName,
hash = it.hash,
loginId = it.loginId,
role = it.role,
unitId = it.reportingUnitId ?: 0
)
}

View file

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
ReportingUnit(
studentId = student.id.toInt(),
unitId = it.id,
roles = it.roles,
senderId = it.senderId,
senderName = it.senderName,
shortName = it.short
)
}

View file

@ -7,7 +7,6 @@ fun List<SdkSemester>.mapToEntities(studentId: Int) = map {
Semester(
studentId = studentId,
diaryId = it.diaryId,
kindergartenDiaryId = it.kindergartenDiaryId,
diaryName = it.diaryName,
schoolYear = it.schoolYear,
semesterId = it.semesterId,

View file

@ -2,7 +2,7 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import java.time.Instant
import java.time.LocalDateTime
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
@ -24,7 +24,7 @@ fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) =
scrapperBaseUrl = it.scrapperBaseUrl,
loginType = it.loginType.name,
isCurrent = false,
registrationDate = Instant.now(),
registrationDate = LocalDateTime.now(),
mobileBaseUrl = it.mobileBaseUrl,
privateKey = it.privateKey,
certificateKey = it.certificateKey,

View file

@ -21,8 +21,8 @@ fun List<SdkTimetable>.mapToEntities(semester: Semester) = map {
studentId = semester.studentId,
diaryId = semester.diaryId,
number = it.number,
start = it.startZoned.toInstant(),
end = it.endZoned.toInstant(),
start = it.start,
end = it.end,
date = it.date,
subject = it.subject,
subjectOld = it.subjectOld,
@ -45,8 +45,8 @@ fun List<SdkTimetableAdditional>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId,
subject = it.subject,
date = it.date,
start = it.startZoned.toInstant(),
end = it.endZoned.toInstant(),
start = it.start,
end = it.end
)
}

View file

@ -1,10 +1,10 @@
package io.github.wulkanowy.data.pojos
import android.content.Intent
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
data class NotificationData(
val destination: Destination,
val intentToStart: Intent,
val title: String,
val content: String
)
@ -13,7 +13,7 @@ data class GroupNotificationData(
val notificationDataList: List<NotificationData>,
val title: String,
val content: String,
val destination: Destination,
val intentToStart: Intent,
val type: NotificationType
)

View file

@ -3,8 +3,9 @@ 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.data.networkBoundResource
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
@ -13,18 +14,23 @@ import javax.inject.Singleton
class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao,
private val appInfo: AppInfo
private val appInfo: AppInfo,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
suspend fun getAdminMessages(student: Student) = networkBoundResource(
private val cacheKey = "admin_messages"
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true },
shouldFetch = {
refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
},
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
refreshHelper.updateLastRefreshTimestamp(cacheKey)
},
showSavedOnLoading = false,
mapResult = { adminMessages ->

View file

@ -5,10 +5,15 @@ import io.github.wulkanowy.data.db.entities.Attendance
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
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
@ -37,7 +42,6 @@ class AttendanceRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
@ -48,8 +52,7 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
},
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester)
},
@ -87,8 +90,7 @@ class AttendanceRepository @Inject constructor(
timeId = attendance.timeId
)
}
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.excuseForAbsence(items, reason)
}
}

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
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.data.networkBoundResource
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
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -32,15 +32,13 @@ class AttendanceSummaryRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired
},
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId)
},

View file

@ -4,9 +4,14 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -31,7 +36,6 @@ class CompletedLessonsRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
@ -47,8 +51,7 @@ class CompletedLessonsRepository @Inject constructor(
)
},
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester)
},

View file

@ -5,15 +5,17 @@ import io.github.wulkanowy.data.db.entities.Conference
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.data.networkBoundResource
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Inject
import javax.inject.Singleton
@ -33,10 +35,9 @@ class ConferenceRepository @Inject constructor(
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false,
startDate: Instant = Instant.EPOCH,
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired
@ -45,8 +46,7 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
},
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
@ -66,7 +66,7 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
startDate = Instant.EPOCH,
startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
)
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)

View file

@ -5,9 +5,14 @@ import io.github.wulkanowy.data.db.entities.Exam
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
@ -34,7 +39,6 @@ class ExamRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
@ -50,8 +54,7 @@ class ExamRepository @Inject constructor(
)
},
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester)
},

View file

@ -7,14 +7,17 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.*
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import java.time.Instant
import java.time.LocalDateTime
import javax.inject.Inject
import javax.inject.Singleton
@ -37,10 +40,6 @@ class GradeRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = {
//When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty()
},
shouldFetch = { (details, summaries) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
@ -52,7 +51,7 @@ class GradeRepository @Inject constructor(
},
fetch = {
val (details, summary) = sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.switchDiary(semester.diaryId, semester.schoolYear)
.getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester)
@ -71,8 +70,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 {
@ -102,13 +101,13 @@ class GradeRepository @Inject constructor(
}
summary.predictedGradeLastChange = when {
oldSummary == null -> Instant.now()
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
oldSummary == null -> LocalDateTime.now()
summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now()
else -> oldSummary.predictedGradeLastChange
}
summary.finalGradeLastChange = when {
oldSummary == null -> Instant.now()
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
oldSummary == null -> LocalDateTime.now()
summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now()
else -> oldSummary.finalGradeLastChange
}
})

View file

@ -11,14 +11,14 @@ import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems
import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
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
import kotlinx.coroutines.sync.Mutex
import java.util.*
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@ -46,7 +46,6 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = partialMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(partialCacheKey, semester)
@ -55,8 +54,7 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester)
},
@ -68,16 +66,20 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items ->
when (subjectName) {
"Wszystkie" -> {
val summaryItem = GradePartialStatistics(
val numerator = items.map {
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
classAverage = items.map { it.classAverage }.getSummaryAverage(),
studentAverage = items.map { it.studentAverage }.getSummaryAverage(),
classAverage = if (numerator.isEmpty()) "" else numerator.average().let {
"%.2f".format(Locale.FRANCE, it)
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)
listOf(summaryItem) + items
)).reversed()
}
else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems()
@ -91,7 +93,6 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = semesterMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(semesterCacheKey, semester)
@ -100,8 +101,7 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester)
},
@ -114,29 +114,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item ->
item.copy().apply {
val denominator = item.amounts.sum()
classAverage = if (denominator == 0) "" else {
average = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount
}.sum().toDouble() / denominator).asAverageString()
}.sum().toDouble() / denominator).let {
"%.2f".format(Locale.FRANCE, it)
}
}
}
}
when (subjectName) {
"Wszystkie" -> {
val summaryItem = GradeSemesterStatistics(
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
studentGrade = 0,
studentGrade = 0
).apply {
classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage()
studentAverage = items
.mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } }
.average().asAverageString()
}
listOf(summaryItem) + itemsWithAverage
average = itemsWithAverage.mapNotNull {
it.average.replace(",", ".").toDoubleOrNull()
}.average().let {
"%.2f".format(Locale.FRANCE, it)
}
}).reversed()
else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems()
}
@ -149,15 +149,13 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = pointsMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
it.isEmpty() || forceRefresh || isExpired
},
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester)
},
@ -174,19 +172,6 @@ class GradeStatisticsRepository @Inject constructor(
}
)
private fun List<String>.getSummaryAverage(): String {
val averages = mapNotNull {
it.replace(",", ".").toDoubleOrNull()
}
return averages.average()
.asAverageString()
.takeIf { averages.isNotEmpty() }
.orEmpty()
}
private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this)
private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach {

View file

@ -5,9 +5,14 @@ import io.github.wulkanowy.data.db.entities.Homework
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -33,7 +38,6 @@ class HomeworkRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
@ -49,8 +53,7 @@ class HomeworkRepository @Inject constructor(
)
},
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},

View file

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
@ -29,7 +29,6 @@ class LuckyNumberRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) },
fetch = {

View file

@ -1,48 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox
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
import javax.inject.Singleton
@Singleton
class MailboxRepository @Inject constructor(
private val mailboxDao: MailboxDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val cacheKey = "mailboxes"
suspend fun refreshMailboxes(student: Student) {
val new = sdk.init(student).getMailboxes().mapToEntities(student)
val old = mailboxDao.loadAll(student.userLoginId)
mailboxDao.deleteAll(old uniqueSubtract new)
mailboxDao.insertAll(new uniqueSubtract old)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
suspend fun getMailbox(student: Student): Mailbox {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
val mailbox = mailboxDao.load(student.userLoginId, student.studentName)
return if (isExpired || mailbox == null) {
refreshMailboxes(student)
val newMailbox = mailboxDao.load(student.userLoginId, student.studentName)
requireNotNull(newMailbox) {
"Mailbox for ${student.userName} - ${student.studentName} not found!"
}
newMailbox
} else mailbox
}
}

View file

@ -7,27 +7,31 @@ import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.SentMessage
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
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
import javax.inject.Singleton
@ -49,69 +53,89 @@ class MessageRepository @Inject constructor(
@Suppress("UNUSED_PARAMETER")
fun getMessages(
student: Student,
mailbox: Mailbox,
semester: Semester,
folder: MessageFolder,
forceRefresh: Boolean,
notify: Boolean = false,
): Flow<Resource<List<Message>>> = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student, folder)
)
it.isEmpty() || forceRefresh || isExpired
},
query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
fetch = {
sdk.init(student).getMessages(Folder.valueOf(folder.name)).mapToEntities(mailbox)
sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now())
.mapToEntities(student)
},
saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach {
it.isNotified = !notify
})
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
}
)
private fun getMessagesWithReadByChange(
old: List<Message>,
new: List<Message>,
setNotified: Boolean
): List<Message> {
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) }
val updatedItems = newMeta uniqueSubtract oldMeta
return updatedItems.map {
val oldItem = old.find { item -> item.messageId == it.first.messageId }
it.first.apply {
id = oldItem?.id ?: 0
isNotified = oldItem?.isNotified ?: setNotified
content = oldItem?.content.orEmpty()
}
}
}
fun getMessage(
student: Student,
message: Message,
markAsRead: Boolean = false,
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
isResultEmpty = { it?.message?.content.isNullOrBlank() },
shouldFetch = {
checkNotNull(it) { "This message no longer exist!" }
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
it.message.unread || it.message.content.isBlank()
checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isEmpty()
},
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) },
fetch = {
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey)
sdk.init(student).getMessageDetails(
messageId = it!!.message.messageId,
folderId = message.folderId,
read = markAsRead,
id = message.realId
).let { details ->
details.content to details.attachments.mapToEntities()
}
},
saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" }
messagesDb.updateAll(
listOf(old.message.apply {
id = message.id
saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" })
messagesDb.updateAll(listOf(old.message.apply {
id = old.message.id
unread = !markAsRead
sender = new.sender
recipients = new.recipients.firstOrNull() ?: "Wielu adresoatów"
content = content.ifBlank { new.content }
})
)
messageAttachmentDao.insertAttachments(
items = new.attachments.mapToEntities(message.messageGlobalKey),
)
content = content.ifBlank { downloadedMessage }
}))
messageAttachmentDao.insertAttachments(attachments)
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
}
)
fun getMessagesFromDatabase(mailbox: Mailbox): Flow<List<Message>> {
return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
fun getMessagesFromDatabase(student: Student): Flow<List<Message>> {
return messagesDb.loadAll(student.id.toInt(), RECEIVED.id)
}
suspend fun updateMessages(messages: List<Message>) {
@ -123,47 +147,24 @@ class MessageRepository @Inject constructor(
subject: String,
content: String,
recipients: List<Recipient>,
mailboxId: String,
) {
sdk.init(student).sendMessage(
): SentMessage = sdk.init(student).sendMessage(
subject = subject,
content = content,
recipients = recipients.mapFromEntities(),
mailboxId = mailboxId,
)
}
suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List<Message>) {
val firstMessage = messages.first()
sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey },
removeForever = firstMessage.folderId == TRASHED.id,
recipients = recipients.mapFromEntities()
)
if (firstMessage.folderId != TRASHED.id) {
val deletedMessages = messages.map {
it.copy(folderId = TRASHED.id)
.apply {
id = it.id
content = it.content
sender = it.sender
recipients = it.recipients
}
}
suspend fun deleteMessage(student: Student, message: Message) {
val isDeleted = sdk.init(student).deleteMessages(
messages = listOf(message.messageId), message.folderId
)
messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages)
getMessages(
student = student,
mailbox = mailbox,
folder = TRASHED,
forceRefresh = true,
).first()
if (message.folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply {
id = message.id
content = message.content
}
suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
deleteMessages(student, mailbox, listOf(message))
messagesDb.updateAll(listOf(deletedMessage))
} else messagesDb.deleteAll(listOf(message))
}
var draftMessage: MessageDraft?

View file

@ -6,12 +6,12 @@ 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.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken
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
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -34,17 +34,15 @@ class MobileDeviceRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired
},
query = { mobileDb.loadAll(student.userLoginId) },
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getRegisteredDevices()
.mapToEntities(student)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
mobileDb.deleteAll(old uniqueSubtract new)
@ -55,16 +53,14 @@ class MobileDeviceRepository @Inject constructor(
)
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device))
}
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getToken()
.mapToMobileDeviceToken()
}

View file

@ -5,9 +5,12 @@ import io.github.wulkanowy.data.db.entities.Note
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.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.*
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -31,7 +34,6 @@ class NoteRepository @Inject constructor(
notify: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
getRefreshKey(cacheKey, semester)
@ -40,8 +42,7 @@ class NoteRepository @Inject constructor(
},
query = { noteDb.loadAll(student.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getNotes(semester.semesterId)
.mapToEntities(semester)
},

View file

@ -7,16 +7,24 @@ import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.TimetableMode
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.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.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject
import javax.inject.Singleton
@ -125,12 +133,6 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_notification_piggyback
)
val isNotificationPiggybackRemoveOriginalEnabled: Boolean
get() = getBoolean(
R.string.pref_key_notifications_piggyback_cancel_original,
R.bool.pref_default_notification_piggyback_cancel_original
)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
@ -200,10 +202,10 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_optional_arithmetic_average
)
var lasSyncDate: Instant?
var lasSyncDate: LocalDateTime
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
.takeIf { it != 0L }?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit().putLong("last_sync_date", value?.toEpochMilli() ?: 0).apply()
.toLocalDateTime()
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
get() {
@ -222,31 +224,19 @@ class PreferencesRepository @Inject constructor(
get() = selectedDashboardTilesPreference.asFlow()
.map { set ->
set.map { DashboardItem.Tile.valueOf(it) }
.plus(
listOfNotNull(
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.plus(DashboardItem.Tile.ACCOUNT)
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
.toSet()
}
var selectedDashboardTiles: Set<DashboardItem.Tile>
get() = selectedDashboardTilesPreference.get()
.map { DashboardItem.Tile.valueOf(it) }
.plus(
listOfNotNull(
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.plus(DashboardItem.Tile.ACCOUNT)
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
.toSet()
set(value) {
val filteredValue = value.filterNot {
it == DashboardItem.Tile.ACCOUNT || it == DashboardItem.Tile.ADMIN_MESSAGE
}
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
.map { it.name }
.toSet()
@ -274,47 +264,15 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
var inAppReviewDate: Instant?
var inAppReviewDate: LocalDate?
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit {
putLong(PREF_KEY_IN_APP_REVIEW_DATE, value?.toEpochMilli() ?: 0)
}
?.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)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value) }
var isAppSupportShown: Boolean
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
var isAgreeToProcessData: Boolean
get() = getBoolean(
R.string.pref_key_ads_consent_data_processing,
R.bool.pref_default_ads_consent_data_processing
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
}
var isPersonalizedAdsEnabled: Boolean
get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) }
val isAdsEnabledFlow = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_ads_enabled),
context.resources.getBoolean(R.bool.pref_default_ads_enabled)
).asFlow()
var isAdsEnabled: Boolean
get() = getBoolean(
R.string.pref_key_ads_enabled,
R.bool.pref_default_ads_enabled
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
}
set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply()
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
@ -344,10 +302,6 @@ class PreferencesRepository @Inject constructor(
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
}
}

View file

@ -1,7 +1,10 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
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
@ -20,10 +23,9 @@ class RecipientRepository @Inject constructor(
private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
val new = sdk.init(student).getRecipients(mailbox.globalKey)
.mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey)
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)
@ -31,27 +33,18 @@ class RecipientRepository @Inject constructor(
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
suspend fun getRecipients(
student: Student,
mailbox: Mailbox,
type: MailboxType
): List<Recipient> {
val cached = recipientDb.loadAll(type, mailbox.globalKey)
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) {
refreshRecipients(student, mailbox, type)
recipientDb.loadAll(type, mailbox.globalKey)
refreshRecipients(student, unit, role)
recipientDb.loadAll(unit.studentId, unit.unitId, role)
} else cached
}
suspend fun getMessageSender(
student: Student,
mailbox: Mailbox,
message: Message
): List<Recipient> = sdk.init(student)
.getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
.mapToEntities(mailbox.globalKey)
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
.mapToEntities(student.studentId)
}
}

View file

@ -0,0 +1,42 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
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.init
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ReportingUnitRepository @Inject constructor(
private val reportingUnitDb: ReportingUnitDao,
private val sdk: Sdk
) {
suspend fun refreshReportingUnits(student: Student) {
val new = sdk.init(student).getReportingUnits().mapToEntities(student)
val old = reportingUnitDb.load(student.id.toInt())
reportingUnitDb.deleteAll(old.uniqueSubtract(new))
reportingUnitDb.insertAll(new.uniqueSubtract(old))
}
suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
return reportingUnitDb.load(student.id.toInt()).ifEmpty {
refreshReportingUnits(student)
reportingUnitDb.load(student.id.toInt())
}
}
suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? {
return reportingUnitDb.loadOne(student.id.toInt(), unitId) ?: run {
refreshReportingUnits(student)
return reportingUnitDb.loadOne(student.id.toInt(), unitId)
}
}
}

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
@ -28,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
fun getSchoolAnnouncements(
student: Student,
forceRefresh: Boolean,
notify: Boolean = false
forceRefresh: Boolean, notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired
},
query = {
schoolAnnouncementDb.loadAll(student.userLoginId)
schoolAnnouncementDb.loadAll(student.studentId)
},
fetch = {
sdk.init(student)
@ -57,7 +55,7 @@ class SchoolAnnouncementRepository @Inject constructor(
)
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
return schoolAnnouncementDb.loadAll(student.userLoginId)
return schoolAnnouncementDb.loadAll(student.studentId)
}
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolDao
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.data.networkBoundResource
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
import javax.inject.Inject
import javax.inject.Singleton
@ -30,7 +30,6 @@ class SchoolRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student)
@ -39,9 +38,7 @@ class SchoolRepository @Inject constructor(
},
query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSchool()
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
.mapToEntity(semester)
},
saveFetchResult = { old, new ->

View file

@ -5,7 +5,11 @@ 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.*
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
@ -39,14 +43,10 @@ class SemesterRepository @Inject constructor(
): Boolean {
val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = when {
Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> {
semesters.firstOrNull { it.isCurrent }?.let {
0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true
}
else -> false
}
val isRefreshOnModeChangeRequired =
if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
} else false
val isRefreshOnNoCurrentAppropriate =
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }

View file

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -25,12 +25,10 @@ class StudentInfoRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getStudentInfo().mapToEntity(semester)
},
saveFetchResult = { old, new ->

View file

@ -73,15 +73,6 @@ class StudentRepository @Inject constructor(
}
}
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) =
studentDb.loadStudentWithSemestersById(id)?.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) {
decrypt(student.password)
}
}
}
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
@ -137,7 +128,4 @@ class StudentRepository @Inject constructor(
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
studentDb.update(studentNickAndAvatar)
suspend fun isOneUniqueStudent() = getSavedStudents(false)
.distinctBy { it.student.studentName }.size == 1
}

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SubjectDao
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.data.networkBoundResource
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
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -31,15 +31,13 @@ class SubjectRepository @Inject constructor(
forceRefresh: Boolean = false,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired
},
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getSubjects().mapToEntities(semester)
},
saveFetchResult = { old, new ->

View file

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
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.data.networkBoundResource
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
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -31,15 +31,13 @@ class TeacherRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired
},
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getTeachers(semester.semesterId)
.mapToEntities(semester)
},

View file

@ -3,13 +3,22 @@ package io.github.wulkanowy.data.repositories
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.*
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
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.flow.combine
import kotlinx.coroutines.sync.Mutex
@ -31,10 +40,6 @@ class TimetableRepository @Inject constructor(
private val cacheKey = "timetable"
enum class TimetableType {
NORMAL, ADDITIONAL
}
fun getTimetable(
student: Student,
semester: Semester,
@ -42,16 +47,9 @@ class TimetableRepository @Inject constructor(
end: LocalDate,
forceRefresh: Boolean,
refreshAdditional: Boolean = false,
notify: Boolean = false,
timetableType: TimetableType = TimetableType.NORMAL
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = {
when (timetableType) {
TimetableType.NORMAL -> it.lessons.isEmpty()
TimetableType.ADDITIONAL -> it.additional.isEmpty()
}
},
shouldFetch = { (timetable, additional, headers) ->
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
@ -64,7 +62,7 @@ class TimetableRepository @Inject constructor(
query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = {
val timetableFull = sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.switchDiary(semester.diaryId, semester.schoolYear)
.getTimetableFull(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
@ -154,8 +152,7 @@ class TimetableRepository @Inject constructor(
old: List<TimetableAdditional>,
new: List<TimetableAdditional>
) {
val oldFiltered = old.filter { !it.isAddedByUser }
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
timetableAdditionalDb.deleteAll(old uniqueSubtract new)
timetableAdditionalDb.insertAll(new uniqueSubtract old)
}
@ -163,14 +160,4 @@ class TimetableRepository @Inject constructor(
timetableHeaderDb.deleteAll(old uniqueSubtract new)
timetableHeaderDb.insertAll(new uniqueSubtract old)
}
suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) =
timetableAdditionalDb.insertAll(additionalList)
suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) =
if (deleteSeries) {
timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!)
} else {
timetableAdditionalDb.deleteAll(listOf(additional))
}
}

View file

@ -1,32 +0,0 @@
package io.github.wulkanowy.data.serializers
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.nullable
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.LocalDate
@OptIn(ExperimentalSerializationApi::class)
object LocalDateSerializer : KSerializer<LocalDate?> {
override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG).nullable
override fun serialize(encoder: Encoder, value: LocalDate?) {
if (value == null) {
encoder.encodeNull()
} else {
encoder.encodeNotNullMark()
encoder.encodeLong(value.toEpochDay())
}
}
override fun deserialize(decoder: Decoder): LocalDate? =
if (decoder.decodeNotNullMark()) {
LocalDate.ofEpochDay(decoder.decodeLong())
} else {
decoder.decodeNull()
}
}

View file

@ -0,0 +1,9 @@
package io.github.wulkanowy.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class HiltBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {}
}

View file

@ -1,7 +1,6 @@
package io.github.wulkanowy.services.alarm
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
@ -10,23 +9,26 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
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.utils.flowWithResource
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.toLocalDateTime
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class TimetableNotificationReceiver : BroadcastReceiver() {
class TimetableNotificationReceiver : HiltBroadcastReceiver() {
@Inject
lateinit var studentRepository: StudentRepository
@ -39,8 +41,6 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
const val NOTIFICATION_TYPE_UPCOMING = 2
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
// FIXME only shows one notification even if there are multiple students.
// Probably want to fix after #721 is merged.
const val NOTIFICATION_ID = 2137
const val STUDENT_NAME = "student_name"
@ -56,24 +56,20 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
@OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
Timber.d("Receiving intent... ${intent.toUri(0)}")
resourceFlow {
val showStudentName = !studentRepository.isOneUniqueStudent()
flowWithResource {
val student = studentRepository.getCurrentStudent(false)
val studentId = intent.getIntExtra(STUDENT_ID, 0)
if (student.studentId == studentId) {
prepareNotification(context, intent, showStudentName)
} else {
Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
}
}
.onResourceError { Timber.e(it) }
.launchIn(GlobalScope)
if (student.studentId == studentId) prepareNotification(context, intent)
else Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
}.onEach {
if (it.status == Status.ERROR) Timber.e(it.error!!)
}.launchIn(GlobalScope)
}
private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) {
private fun prepareNotification(context: Context, intent: Intent) {
val type = intent.getIntExtra(LESSON_TYPE, 0)
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
@ -82,7 +78,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
}
val studentId = intent.getIntExtra(STUDENT_ID, 0)
val studentName = intent.getStringExtra(STUDENT_NAME).takeIf { showStudentName }
val studentName = intent.getStringExtra(STUDENT_NAME)
val subject = intent.getStringExtra(LESSON_TITLE)
val room = intent.getStringExtra(LESSON_ROOM)
@ -93,28 +89,21 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE)
val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM)
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: $start, student: $studentId")
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
val notificationTitleResId =
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next
val notificationTitle =
context.getString(notificationTitleResId, "($room) $subject".removePrefix("()"))
val nextLessonText = nextSubject?.let {
showNotification(
context, isPersistent, 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("()")
)
}
showNotification(
context = context,
isPersistent = isPersistent,
studentName = studentName,
countDown = if (type == NOTIFICATION_TYPE_CURRENT) end else start,
timeout = end - start,
title = notificationTitle,
next = nextLessonText
)
}
@ -141,10 +130,9 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
.setTimeoutAfter(timeout)
.setSmallIcon(R.drawable.ic_stat_timetable)
.setColor(context.getCompatColor(R.color.colorPrimary))
.setStyle(NotificationCompat.InboxStyle()
.addLine(next)
.also { inboxStyle ->
studentName?.let { inboxStyle.setSummaryText(it) }
.setStyle(NotificationCompat.InboxStyle().also {
it.setSummaryText(studentName)
it.addLine(next)
})
.setContentIntent(
PendingIntent.getActivity(

View file

@ -28,12 +28,12 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
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.Duration.ofMinutes
import java.time.Instant
import java.time.Instant.now
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalDateTime.now
import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor(
@ -43,14 +43,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private val dispatchersProvider: DispatchersProvider,
) {
private fun getRequestCode(time: Instant, studentId: Int): Int =
(time.toEpochMilli() * studentId).toInt()
private fun getRequestCode(time: LocalDateTime, studentId: Int) =
(time.toTimestamp() * studentId).toInt()
private fun getUpcomingLessonTime(
index: Int,
day: List<Timetable>,
lesson: Timetable
): Instant = day.getOrNull(index - 1)?.end ?: lesson.start.minus(ofMinutes(30))
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
val studentId = student.studentId
@ -71,7 +71,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
}
}
private fun cancelScheduledTo(range: ClosedRange<Instant>, requestCode: Int) {
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
if (now() in range) cancelNotification()
alarmManager.cancel(
@ -150,8 +150,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
putExtra(STUDENT_ID, student.studentId)
putExtra(STUDENT_NAME, student.nickOrName)
putExtra(LESSON_ROOM, lesson.room)
putExtra(LESSON_START, lesson.start.toEpochMilli())
putExtra(LESSON_END, lesson.end.toEpochMilli())
putExtra(LESSON_START, lesson.start.toTimestamp())
putExtra(LESSON_END, lesson.end.toTimestamp())
putExtra(LESSON_TITLE, lesson.subject)
putExtra(LESSON_NEXT_TITLE, nextLesson?.subject)
putExtra(LESSON_NEXT_ROOM, nextLesson?.room)
@ -162,11 +162,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
intent: Intent,
studentId: Int,
notificationType: Int,
time: Instant
time: LocalDateTime
) {
try {
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, RTC_WAKEUP, time.toEpochMilli(),
alarmManager, RTC_WAKEUP, time.toTimestamp(),
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(LESSON_TYPE, notificationType)
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)

View file

@ -19,9 +19,6 @@ class VulcanNotificationListenerService : NotificationListenerService() {
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
syncManager.startOneTimeSyncWorker()
if (preferenceRepository.isNotificationPiggybackRemoveOriginalEnabled) {
cancelNotification(statusBarNotification.key)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more