Compare commits
123 Commits
Author | SHA1 | Date | |
---|---|---|---|
62c04fb205 | |||
10c36f19bf | |||
37d756b8fe | |||
de1bc4809f | |||
3d6ec93cde | |||
c293c76398 | |||
09e07a1713 | |||
71f1a55437 | |||
d9e22af5ef | |||
bc0689a30d | |||
9d47127921 | |||
08a3bd77bd | |||
9fe1151a04 | |||
793952cb44 | |||
d64a21b50c | |||
274f9dde07 | |||
5a884a4c56 | |||
c55fd98179 | |||
ffc0cd840b | |||
96067946d0 | |||
9339e7d916 | |||
b4117aa62e | |||
dc3a941e24 | |||
b67ecbba4b | |||
1175740ba2 | |||
378ed0100f | |||
cf87339ac4 | |||
c23a90f104 | |||
d337be0f40 | |||
cf7c6f78ea | |||
efa68f5044 | |||
b9be85d99c | |||
dfc4553fc6 | |||
08c9539abe | |||
fd18583df2 | |||
7c4f1c7b22 | |||
bdb6c962ea | |||
f1c217b087 | |||
4b6277abf5 | |||
344e0d55ff | |||
89a6a98bbf | |||
fcc7dc0913 | |||
1b74bffc06 | |||
0f11f14c3e | |||
e70fe6f097 | |||
b70649f136 | |||
7b13684137 | |||
c4689fcbb3 | |||
d8f644c5b4 | |||
c808bf2e61 | |||
0fb55bd6c6 | |||
c5dfea788c | |||
120e5c9171 | |||
a97039a727 | |||
e9ba65f8f6 | |||
a264abf814 | |||
0a2eb07844 | |||
bfab265ccf | |||
cd59166efb | |||
06ed5f6079 | |||
6b70583573 | |||
c3cbaa6ac2 | |||
f61d820d6f | |||
03ad5527f8 | |||
cce736410b | |||
8c515bd03f | |||
8dcb3ed45d | |||
4f3f24ac10 | |||
d074e5c9b3 | |||
d2d1d1dba7 | |||
891e241d1a | |||
5c4a3d578b | |||
fcf0adfd80 | |||
c42a47ac48 | |||
fa48b033af | |||
808927a58a | |||
dc717c9fb5 | |||
a2804d813a | |||
847ab6149a | |||
dc74d2877b | |||
6534176685 | |||
9542b9f231 | |||
dbba61a99f | |||
c2496a15b8 | |||
facf84d9a8 | |||
459c8330f9 | |||
0c8e2632a2 | |||
b17e9deca0 | |||
c296e72c30 | |||
637125e1fc | |||
445bfda801 | |||
ebde42328a | |||
a0bf14b576 | |||
2e7caabde3 | |||
bb052fd4c9 | |||
28ef8c6761 | |||
15e8e096ed | |||
6007de017f | |||
775b5122ef | |||
fed00122d7 | |||
426bee882c | |||
d37de197fc | |||
447ece3696 | |||
a73f39e59c | |||
f912aac140 | |||
3caabd3e0e | |||
88576271e2 | |||
b088551005 | |||
130e11a629 | |||
d5e0ae7b37 | |||
e6f56a74a4 | |||
1bc59cfa7f | |||
41bae262a5 | |||
ae65228805 | |||
391ee6e621 | |||
0a87df3d82 | |||
cb4ae21903 | |||
679cf2554d | |||
d473d53879 | |||
6531061b48 | |||
3347e8fba8 | |||
84067126a1 | |||
da9bebe923 |
21
.github/workflows/deploy-store.yml
vendored
21
.github/workflows/deploy-store.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Deploy to app stores
|
name: Deploy release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
@ -7,16 +7,17 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
deploy-google-play:
|
deploy-google-play:
|
||||||
name: Deploy to google play
|
name: Google Play
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
environment: google-play
|
environment: google-play
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@ -37,20 +38,22 @@ jobs:
|
|||||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||||
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
|
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
|
||||||
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
||||||
|
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
|
||||||
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: Deploy to AppGallery
|
name: AppGallery
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
environment: app-gallery
|
environment: app-gallery
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
|
20
.github/workflows/deploy-test.yml
vendored
20
.github/workflows/deploy-test.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Deploy to app tests
|
name: Deploy DEV
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -18,11 +18,12 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
environment: app-center
|
environment: app-center
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@ -66,7 +67,7 @@ jobs:
|
|||||||
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
||||||
run: ./gradlew assembleFdroidDebug --stacktrace
|
run: ./gradlew assembleFdroidDebug --stacktrace
|
||||||
- name: Upload apk to github artifacts
|
- name: Upload apk to github artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
|
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
|
||||||
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
|
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
|
||||||
@ -87,11 +88,12 @@ jobs:
|
|||||||
environment: app-distribution
|
environment: app-distribution
|
||||||
if: github.event_name != 'pull_request_target'
|
if: github.event_name != 'pull_request_target'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@ -131,7 +133,7 @@ jobs:
|
|||||||
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
|
||||||
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
|
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
|
||||||
- name: Upload apk to github artifacts
|
- name: Upload apk to github artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
|
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
|
||||||
path: app/build/outputs/apk/play/debug/app-play-debug.apk
|
path: app/build/outputs/apk/play/debug/app-play-debug.apk
|
||||||
|
66
.github/workflows/test.yml
vendored
66
.github/workflows/test.yml
vendored
@ -8,18 +8,20 @@ on:
|
|||||||
branches: [ master, develop ]
|
branches: [ master, develop ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit-tests:
|
|
||||||
name: Unit tests
|
tests-fdroid:
|
||||||
|
name: F-Droid
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: fkirc/skip-duplicate-actions@master
|
- uses: fkirc/skip-duplicate-actions@master
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@ -29,6 +31,58 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./gradlew testFdroidDebugUnitTest --stacktrace
|
./gradlew testFdroidDebugUnitTest --stacktrace
|
||||||
./gradlew jacocoTestReport --stacktrace
|
./gradlew jacocoTestReport --stacktrace
|
||||||
- uses: codecov/codecov-action@v1
|
- 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
|
||||||
with:
|
with:
|
||||||
flags: unit
|
flags: unit
|
||||||
|
@ -51,7 +51,7 @@ Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGal
|
|||||||
alt="Explore it on AppGallery"
|
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=)
|
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
|
||||||
|
|
||||||
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
|
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
|
||||||
|
|
||||||
## Gebaut mit
|
## Gebaut mit
|
||||||
|
|
||||||
|
@ -15,33 +15,35 @@ apply from: 'sonarqube.gradle'
|
|||||||
apply from: 'hooks.gradle'
|
apply from: 'hooks.gradle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
namespace 'io.github.wulkanowy'
|
||||||
|
compileSdkVersion 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.github.wulkanowy"
|
applicationId "io.github.wulkanowy"
|
||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 32
|
||||||
versionCode 104
|
versionCode 110
|
||||||
versionName "1.6.0"
|
versionName "1.7.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
|
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||||
admob_project_id: ""
|
admob_project_id: ""
|
||||||
]
|
]
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
annotationProcessorOptions {
|
annotationProcessorOptions {
|
||||||
arguments += [
|
arguments += [
|
||||||
"room.schemaLocation": "$projectDir/schemas".toString(),
|
"room.schemaLocation": "$projectDir/schemas".toString(),
|
||||||
"room.incremental" : "true"
|
"room.incremental" : "true"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
||||||
|
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
|
||||||
|
|
||||||
if (System.env.SET_BUILD_TIMESTAMP) {
|
if (System.env.SET_BUILD_TIMESTAMP) {
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||||
@ -94,10 +96,12 @@ android {
|
|||||||
play {
|
play {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
install_channel : "Google Play",
|
install_channel : "Google Play",
|
||||||
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
|
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", "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 {
|
fdroid {
|
||||||
@ -122,6 +126,8 @@ android {
|
|||||||
|
|
||||||
testOptions.unitTests {
|
testOptions.unitTests {
|
||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
|
// workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750
|
||||||
|
all { jvmArgs '-noverify' }
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -132,12 +138,14 @@ android {
|
|||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
|
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
exclude 'META-INF/library_release.kotlin_module'
|
resources {
|
||||||
exclude 'META-INF/library-core_release.kotlin_module'
|
excludes += ['META-INF/library_release.kotlin_module',
|
||||||
|
'META-INF/library-core_release.kotlin_module']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aboutLibraries {
|
aboutLibraries {
|
||||||
@ -153,8 +161,8 @@ play {
|
|||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'production'
|
track = 'production'
|
||||||
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
|
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
|
||||||
userFraction = 0.25d
|
userFraction = 0.95d
|
||||||
updatePriority = 1
|
updatePriority = 5
|
||||||
enabled.set(false)
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,34 +179,34 @@ huaweiPublish {
|
|||||||
ext {
|
ext {
|
||||||
work_manager = "2.7.1"
|
work_manager = "2.7.1"
|
||||||
android_hilt = "1.0.0"
|
android_hilt = "1.0.0"
|
||||||
room = "2.4.2"
|
room = "2.4.3"
|
||||||
chucker = "3.5.2"
|
chucker = "3.5.2"
|
||||||
mockk = "1.12.2"
|
mockk = "1.12.5"
|
||||||
coroutines = "1.6.0"
|
coroutines = "1.6.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:1.6.0"
|
implementation "io.github.wulkanowy:sdk:1.7.0"
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.7.0"
|
implementation "androidx.core:core-ktx:1.8.0"
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
|
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
implementation "androidx.activity:activity-ktx:1.5.1"
|
||||||
implementation "androidx.appcompat:appcompat:1.4.1"
|
implementation "androidx.appcompat:appcompat:1.5.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||||
implementation "androidx.annotation:annotation:1.3.0"
|
implementation "androidx.annotation:annotation:1.4.0"
|
||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
|
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||||
implementation "com.google.android.material:material:1.5.0"
|
implementation "com.google.android.material:material:1.6.1"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
||||||
@ -206,7 +214,7 @@ dependencies {
|
|||||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room"
|
implementation "androidx.room:room-runtime:$room"
|
||||||
implementation "androidx.room:room-ktx:$room"
|
implementation "androidx.room:room-ktx:$room"
|
||||||
@ -222,27 +230,27 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
|
||||||
|
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
||||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||||
implementation "io.coil-kt:coil:1.4.0"
|
implementation "io.coil-kt:coil:2.2.0"
|
||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
|
implementation 'com.fredporciuncula:flow-preferences:1.8.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:29.3.0')
|
playImplementation platform('com.google.firebase:firebase-bom:30.3.2')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
playImplementation 'com.google.android.play:core:1.10.3'
|
playImplementation 'com.google.android.play:core:1.10.3'
|
||||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||||
playImplementation 'com.google.android.gms:play-services-ads:20.6.0'
|
playImplementation 'com.google.android.gms:play-services-ads:21.1.0'
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.300'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300'
|
||||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200'
|
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300'
|
||||||
|
|
||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
|
|
||||||
@ -255,7 +263,7 @@ dependencies {
|
|||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
testImplementation 'org.robolectric:robolectric:4.8.1'
|
||||||
testImplementation "androidx.test:runner:1.4.0"
|
testImplementation "androidx.test:runner:1.4.0"
|
||||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||||
testImplementation "androidx.test:core:1.4.0"
|
testImplementation "androidx.test:core:1.4.0"
|
||||||
|
2445
app/schemas/io.github.wulkanowy.data.db.AppDatabase/49.json
Normal file
2445
app/schemas/io.github.wulkanowy.data.db.AppDatabase/49.json
Normal file
File diff suppressed because it is too large
Load Diff
2445
app/schemas/io.github.wulkanowy.data.db.AppDatabase/50.json
Normal file
2445
app/schemas/io.github.wulkanowy.data.db.AppDatabase/50.json
Normal file
File diff suppressed because it is too large
Load Diff
2409
app/schemas/io.github.wulkanowy.data.db.AppDatabase/51.json
Normal file
2409
app/schemas/io.github.wulkanowy.data.db.AppDatabase/51.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,92 @@
|
|||||||
{
|
{
|
||||||
"agcgw":{
|
"agcgw": {
|
||||||
"backurl":"connect-dre.dbankcloud.cn",
|
"backurl": "connect-dre.hispace.hicloud.com",
|
||||||
"url":"connect-dre.hispace.hicloud.com"
|
"url": "connect-dre.dbankcloud.cn",
|
||||||
},
|
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
|
||||||
"client":{
|
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
|
||||||
"cp_id":"890048000024105546",
|
},
|
||||||
"product_id":"",
|
"agcgw_all": {
|
||||||
"client_id":"",
|
"CN": "connect-drcn.dbankcloud.cn",
|
||||||
"client_secret":"",
|
"CN_back": "connect-drcn.hispace.hicloud.com",
|
||||||
"app_id":"101440411",
|
"DE": "connect-dre.dbankcloud.cn",
|
||||||
"package_name":"io.github.wulkanowy.dev",
|
"DE_back": "connect-dre.hispace.hicloud.com",
|
||||||
"api_key":""
|
"RU": "connect-drru.hispace.dbankcloud.ru",
|
||||||
},
|
"RU_back": "connect-drru.hispace.dbankcloud.cn",
|
||||||
"service":{
|
"SG": "connect-dra.dbankcloud.cn",
|
||||||
"analytics":{
|
"SG_back": "connect-dra.hispace.hicloud.com"
|
||||||
"collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
},
|
||||||
"resource_id":"p1",
|
"websocketgw_all": {
|
||||||
"channel_id":""
|
"CN": "connect-ws-drcn.hispace.dbankcloud.cn",
|
||||||
},
|
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
|
||||||
|
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
|
||||||
|
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
|
||||||
|
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
|
||||||
|
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
|
||||||
|
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
|
||||||
|
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"cp_id": "890048000024105546",
|
||||||
|
"product_id": "736430079244736562",
|
||||||
|
"client_id": "514530959291319360",
|
||||||
|
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
|
||||||
|
"project_id": "736430079244736562",
|
||||||
|
"app_id": "106552551",
|
||||||
|
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
|
||||||
|
"package_name": "io.github.wulkanowy.dev"
|
||||||
|
},
|
||||||
|
"oauth_client": {
|
||||||
|
"client_id": "106552551",
|
||||||
|
"client_type": 1
|
||||||
|
},
|
||||||
|
"app_info": {
|
||||||
|
"app_id": "106552551",
|
||||||
|
"package_name": "io.github.wulkanowy.dev"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"analytics": {
|
||||||
|
"collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||||
|
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
|
||||||
|
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
|
||||||
|
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||||
|
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
|
||||||
|
"resource_id": "p1",
|
||||||
|
"channel_id": ""
|
||||||
|
},
|
||||||
"search":{
|
"search":{
|
||||||
"url":"https://search-dre.cloud.huawei.com"
|
"url":"https://search-dre.cloud.huawei.com"
|
||||||
},
|
},
|
||||||
"cloudstorage":{
|
"cloudstorage": {
|
||||||
"storage_url":"https://ops-dre.agcstorage.link"
|
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
|
||||||
},
|
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
"ml":{
|
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
"mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
|
"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",
|
||||||
"region":"DE",
|
"storage_url_sg": "https://ops-dra.agcstorage.link",
|
||||||
"configuration_version":"1.0"
|
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
|
||||||
|
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
|
||||||
|
},
|
||||||
|
"ml": {
|
||||||
|
"mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"region": "DE",
|
||||||
|
"configuration_version": "3.0",
|
||||||
|
"appInfos": [
|
||||||
|
{
|
||||||
|
"package_name": "io.github.wulkanowy.dev",
|
||||||
|
"client": {
|
||||||
|
"app_id": "106552551"
|
||||||
|
},
|
||||||
|
"app_info": {
|
||||||
|
"package_name": "io.github.wulkanowy.dev",
|
||||||
|
"app_id": "106552551"
|
||||||
|
},
|
||||||
|
"oauth_client": {
|
||||||
|
"client_type": 1,
|
||||||
|
"client_id": "106552551"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
28
app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt
Normal file
28
app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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)
|
28
app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt
Normal file
28
app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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)
|
@ -7,6 +7,7 @@ import javax.inject.Inject
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@Suppress("UNUSED_PARAMETER", "unused")
|
||||||
class InAppReviewHelper @Inject constructor(
|
class InAppReviewHelper @Inject constructor(
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context
|
||||||
) {
|
) {
|
||||||
@ -14,4 +15,4 @@ class InAppReviewHelper @Inject constructor(
|
|||||||
fun showInAppReview(activity: MainActivity) {
|
fun showInAppReview(activity: MainActivity) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="io.github.wulkanowy"
|
|
||||||
android:installLocation="internalOnly">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
@ -31,10 +31,14 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var analyticsHelper: AnalyticsHelper
|
lateinit var analyticsHelper: AnalyticsHelper
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var adsHelper: AdsHelper
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
initializeAppLanguage()
|
initializeAppLanguage()
|
||||||
themeManager.applyDefaultTheme()
|
themeManager.applyDefaultTheme()
|
||||||
|
adsHelper.initialize()
|
||||||
initLogging()
|
initLogging()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
|
|||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
@ -110,7 +109,6 @@ internal class DataModule {
|
|||||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
|
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
|
||||||
@ -197,7 +195,7 @@ internal class DataModule {
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao
|
fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -30,7 +30,7 @@ import javax.inject.Singleton
|
|||||||
Subject::class,
|
Subject::class,
|
||||||
LuckyNumber::class,
|
LuckyNumber::class,
|
||||||
CompletedLesson::class,
|
CompletedLesson::class,
|
||||||
ReportingUnit::class,
|
Mailbox::class,
|
||||||
Recipient::class,
|
Recipient::class,
|
||||||
MobileDevice::class,
|
MobileDevice::class,
|
||||||
Teacher::class,
|
Teacher::class,
|
||||||
@ -55,7 +55,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 48
|
const val VERSION_SCHEMA = 51
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
@ -102,6 +102,9 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration43(),
|
Migration43(),
|
||||||
Migration44(),
|
Migration44(),
|
||||||
Migration46(),
|
Migration46(),
|
||||||
|
Migration49(),
|
||||||
|
Migration50(),
|
||||||
|
Migration51(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
@ -152,7 +155,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
abstract val completedLessonsDao: CompletedLessonsDao
|
abstract val completedLessonsDao: CompletedLessonsDao
|
||||||
|
|
||||||
abstract val reportingUnitDao: ReportingUnitDao
|
abstract val mailboxDao: MailboxDao
|
||||||
|
|
||||||
abstract val recipientDao: RecipientDao
|
abstract val recipientDao: RecipientDao
|
||||||
|
|
||||||
|
@ -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.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?
|
||||||
|
}
|
@ -11,9 +11,9 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
interface MessagesDao : BaseDao<Message> {
|
interface MessagesDao : BaseDao<Message> {
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
|
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
|
||||||
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
|
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
|
||||||
|
|
||||||
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
|
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||||
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>
|
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
@Dao
|
@Dao
|
||||||
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
||||||
|
|
||||||
@Query("SELECT * FROM MobileDevices WHERE student_id = :userLoginId ORDER BY date DESC")
|
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||||
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
|
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao
|
|||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -9,6 +10,6 @@ import javax.inject.Singleton
|
|||||||
@Dao
|
@Dao
|
||||||
interface RecipientDao : BaseDao<Recipient> {
|
interface RecipientDao : BaseDao<Recipient> {
|
||||||
|
|
||||||
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role")
|
@Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey")
|
||||||
suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient>
|
suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List<Recipient>
|
||||||
}
|
}
|
||||||
|
@ -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.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?
|
|
||||||
}
|
|
@ -10,6 +10,6 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
||||||
|
|
||||||
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId ORDER BY date DESC")
|
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
||||||
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
|
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
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,
|
||||||
|
}
|
@ -9,23 +9,16 @@ import java.time.Instant
|
|||||||
@Entity(tableName = "Messages")
|
@Entity(tableName = "Messages")
|
||||||
data class Message(
|
data class Message(
|
||||||
|
|
||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "message_global_key")
|
||||||
val studentId: Long,
|
val messageGlobalKey: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "real_id")
|
@ColumnInfo(name = "mailbox_key")
|
||||||
val realId: Int,
|
val mailboxKey: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "message_id")
|
@ColumnInfo(name = "message_id")
|
||||||
val messageId: Int,
|
val messageId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = "sender_name")
|
val correspondents: String,
|
||||||
val sender: String,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "sender_id")
|
|
||||||
val senderId: Int,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "recipient_name")
|
|
||||||
val recipient: String,
|
|
||||||
|
|
||||||
val subject: String,
|
val subject: String,
|
||||||
|
|
||||||
@ -36,8 +29,6 @@ data class Message(
|
|||||||
|
|
||||||
var unread: Boolean,
|
var unread: Boolean,
|
||||||
|
|
||||||
val removed: Boolean,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "has_attachments")
|
@ColumnInfo(name = "has_attachments")
|
||||||
val hasAttachments: Boolean
|
val hasAttachments: Boolean
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
@ -48,11 +39,7 @@ data class Message(
|
|||||||
@ColumnInfo(name = "is_notified")
|
@ColumnInfo(name = "is_notified")
|
||||||
var isNotified: Boolean = true
|
var isNotified: Boolean = true
|
||||||
|
|
||||||
@ColumnInfo(name = "unread_by")
|
|
||||||
var unreadBy: Int = 0
|
|
||||||
|
|
||||||
@ColumnInfo(name = "read_by")
|
|
||||||
var readBy: Int = 0
|
|
||||||
|
|
||||||
var content: String = ""
|
var content: String = ""
|
||||||
|
var sender: String? = null
|
||||||
|
var recipients: String? = null
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,8 @@ data class MessageAttachment(
|
|||||||
@ColumnInfo(name = "real_id")
|
@ColumnInfo(name = "real_id")
|
||||||
val realId: Int,
|
val realId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = "message_id")
|
@ColumnInfo(name = "message_global_key")
|
||||||
val messageId: Int,
|
val messageGlobalKey: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "one_drive_id")
|
|
||||||
val oneDriveId: String,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "url")
|
@ColumnInfo(name = "url")
|
||||||
val url: String,
|
val url: String,
|
||||||
|
@ -7,6 +7,6 @@ data class MessageWithAttachment(
|
|||||||
@Embedded
|
@Embedded
|
||||||
val message: Message,
|
val message: Message,
|
||||||
|
|
||||||
@Relation(parentColumn = "message_id", entityColumn = "message_id")
|
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
|
||||||
val attachments: List<MessageAttachment>
|
val attachments: List<MessageAttachment>
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ import java.time.Instant
|
|||||||
@Entity(tableName = "MobileDevices")
|
@Entity(tableName = "MobileDevices")
|
||||||
data class MobileDevice(
|
data class MobileDevice(
|
||||||
|
|
||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "user_login_id")
|
||||||
val userLoginId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = "device_id")
|
@ColumnInfo(name = "device_id")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.data.db.entities
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
@ -8,32 +7,16 @@ import java.io.Serializable
|
|||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
@Entity(tableName = "Recipients")
|
@Entity(tableName = "Recipients")
|
||||||
data class Recipient(
|
data class Recipient(
|
||||||
|
val mailboxGlobalKey: String,
|
||||||
@ColumnInfo(name = "student_id")
|
val studentMailboxGlobalKey: String,
|
||||||
val studentId: Int,
|
val fullName: String,
|
||||||
|
val userName: String,
|
||||||
@ColumnInfo(name = "real_id")
|
val schoolShortName: String,
|
||||||
val realId: String,
|
val type: MailboxType,
|
||||||
|
|
||||||
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 {
|
) : Serializable {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
override fun toString() = name
|
override fun toString() = userName
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -9,8 +9,8 @@ import java.time.LocalDate
|
|||||||
@Entity(tableName = "SchoolAnnouncements")
|
@Entity(tableName = "SchoolAnnouncements")
|
||||||
data class SchoolAnnouncement(
|
data class SchoolAnnouncement(
|
||||||
|
|
||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "user_login_id")
|
||||||
val studentId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,10 @@ package io.github.wulkanowy.data.enums
|
|||||||
|
|
||||||
enum class GradeSortingMode(val value: String) {
|
enum class GradeSortingMode(val value: String) {
|
||||||
ALPHABETIC("alphabetic"),
|
ALPHABETIC("alphabetic"),
|
||||||
DATE("date");
|
DATE("date"),
|
||||||
|
AVERAGE("average");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC
|
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio
|
|||||||
|
|
||||||
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
||||||
SchoolAnnouncement(
|
SchoolAnnouncement(
|
||||||
studentId = student.userLoginId,
|
userLoginId = student.userLoginId,
|
||||||
date = it.date,
|
date = it.date,
|
||||||
subject = it.subject,
|
subject = it.subject,
|
||||||
content = it.content,
|
content = it.content,
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
@ -1,40 +1,31 @@
|
|||||||
package io.github.wulkanowy.data.mappers
|
package io.github.wulkanowy.data.mappers
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.*
|
||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.sdk.pojo.MailboxType
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import java.time.Instant
|
|
||||||
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
||||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||||
|
|
||||||
fun List<SdkMessage>.mapToEntities(student: Student) = map {
|
fun List<SdkMessage>.mapToEntities(mailbox: Mailbox) = map {
|
||||||
Message(
|
Message(
|
||||||
studentId = student.id,
|
messageGlobalKey = it.globalKey,
|
||||||
realId = it.id ?: 0,
|
mailboxKey = mailbox.globalKey,
|
||||||
messageId = it.messageId ?: 0,
|
messageId = it.id,
|
||||||
sender = it.sender?.name.orEmpty(),
|
correspondents = it.correspondents,
|
||||||
senderId = it.sender?.loginId ?: 0,
|
|
||||||
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
|
|
||||||
subject = it.subject.trim(),
|
subject = it.subject.trim(),
|
||||||
date = it.dateZoned?.toInstant() ?: Instant.now(),
|
date = it.dateZoned.toInstant(),
|
||||||
folderId = it.folderId,
|
folderId = it.folderId,
|
||||||
unread = it.unread ?: false,
|
unread = it.unread,
|
||||||
removed = it.removed,
|
|
||||||
hasAttachments = it.hasAttachments
|
hasAttachments = it.hasAttachments
|
||||||
).apply {
|
).apply {
|
||||||
content = it.content.orEmpty()
|
content = it.content.orEmpty()
|
||||||
unreadBy = it.unreadBy ?: 0
|
|
||||||
readBy = it.readBy ?: 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<SdkMessageAttachment>.mapToEntities() = map {
|
fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
|
||||||
MessageAttachment(
|
MessageAttachment(
|
||||||
realId = it.id,
|
messageGlobalKey = messageGlobalKey,
|
||||||
messageId = it.messageId,
|
realId = it.url.hashCode(),
|
||||||
oneDriveId = it.oneDriveId,
|
|
||||||
url = it.url,
|
url = it.url,
|
||||||
filename = it.filename
|
filename = it.filename
|
||||||
)
|
)
|
||||||
@ -42,12 +33,11 @@ fun List<SdkMessageAttachment>.mapToEntities() = map {
|
|||||||
|
|
||||||
fun List<Recipient>.mapFromEntities() = map {
|
fun List<Recipient>.mapFromEntities() = map {
|
||||||
SdkRecipient(
|
SdkRecipient(
|
||||||
id = it.realId,
|
fullName = it.fullName,
|
||||||
name = it.realName,
|
userName = it.userName,
|
||||||
loginId = it.loginId,
|
studentName = it.userName,
|
||||||
reportingUnitId = it.unitId,
|
mailboxGlobalKey = it.mailboxGlobalKey,
|
||||||
role = it.role,
|
schoolNameShort = it.schoolShortName,
|
||||||
hash = it.hash,
|
type = MailboxType.valueOf(it.type.name),
|
||||||
shortName = it.name
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package io.github.wulkanowy.data.mappers
|
package io.github.wulkanowy.data.mappers
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||||
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
|
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
|
||||||
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
|
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
|
||||||
|
|
||||||
fun List<SdkDevice>.mapToEntities(semester: Semester) = map {
|
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
||||||
MobileDevice(
|
MobileDevice(
|
||||||
userLoginId = semester.studentId,
|
userLoginId = student.userLoginId,
|
||||||
date = it.createDateZoned.toInstant(),
|
date = it.createDateZoned.toInstant(),
|
||||||
deviceId = it.id,
|
deviceId = it.id,
|
||||||
name = it.name
|
name = it.name
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package io.github.wulkanowy.data.mappers
|
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.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||||
|
|
||||||
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map {
|
fun List<SdkRecipient>.mapToEntities(studentMailboxGlobalKey: String) = map {
|
||||||
Recipient(
|
Recipient(
|
||||||
studentId = userLoginId,
|
mailboxGlobalKey = it.mailboxGlobalKey,
|
||||||
realId = it.id,
|
fullName = it.fullName,
|
||||||
realName = it.name,
|
userName = it.userName,
|
||||||
name = it.shortName,
|
studentMailboxGlobalKey = studentMailboxGlobalKey,
|
||||||
hash = it.hash,
|
schoolShortName = it.schoolNameShort,
|
||||||
loginId = it.loginId,
|
type = MailboxType.valueOf(it.type.name),
|
||||||
role = it.role,
|
|
||||||
unitId = it.reportingUnitId ?: 0
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
@ -0,0 +1,48 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -10,24 +10,24 @@ import io.github.wulkanowy.data.db.dao.MessagesDao
|
|||||||
import io.github.wulkanowy.data.db.entities.*
|
import io.github.wulkanowy.data.db.entities.*
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
import io.github.wulkanowy.data.enums.MessageFolder
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
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.mapFromEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.networkBoundResource
|
import io.github.wulkanowy.data.networkBoundResource
|
||||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Folder
|
import io.github.wulkanowy.sdk.pojo.Folder
|
||||||
import io.github.wulkanowy.sdk.pojo.SentMessage
|
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
import io.github.wulkanowy.utils.getRefreshKey
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDateTime.now
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class MessageRepository @Inject constructor(
|
|||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun getMessages(
|
fun getMessages(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
mailbox: Mailbox,
|
||||||
folder: MessageFolder,
|
folder: MessageFolder,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
@ -62,42 +62,20 @@ class MessageRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now())
|
sdk.init(student).getMessages(Folder.valueOf(folder.name)).mapToEntities(mailbox)
|
||||||
.mapToEntities(student)
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
messagesDb.deleteAll(old uniqueSubtract new)
|
messagesDb.deleteAll(old uniqueSubtract new)
|
||||||
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
||||||
it.isNotified = !notify
|
it.isNotified = !notify
|
||||||
})
|
})
|
||||||
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
|
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
|
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(
|
fun getMessage(
|
||||||
student: Student,
|
student: Student,
|
||||||
message: Message,
|
message: Message,
|
||||||
@ -106,34 +84,34 @@ class MessageRepository @Inject constructor(
|
|||||||
isResultEmpty = { it?.message?.content.isNullOrBlank() },
|
isResultEmpty = { it?.message?.content.isNullOrBlank() },
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
checkNotNull(it) { "This message no longer exist!" }
|
checkNotNull(it) { "This message no longer exist!" }
|
||||||
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
|
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
||||||
it.message.unread || it.message.content.isEmpty()
|
it.message.unread || it.message.content.isBlank()
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) },
|
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).getMessageDetails(
|
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey)
|
||||||
messageId = it!!.message.messageId,
|
|
||||||
folderId = message.folderId,
|
|
||||||
read = markAsRead,
|
|
||||||
id = message.realId
|
|
||||||
).let { details ->
|
|
||||||
details.content to details.attachments.mapToEntities()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, (downloadedMessage, attachments) ->
|
saveFetchResult = { old, new ->
|
||||||
checkNotNull(old) { "Fetched message no longer exist!" }
|
checkNotNull(old) { "Fetched message no longer exist!" }
|
||||||
messagesDb.updateAll(listOf(old.message.apply {
|
messagesDb.updateAll(
|
||||||
id = old.message.id
|
listOf(old.message.apply {
|
||||||
unread = !markAsRead
|
id = message.id
|
||||||
content = content.ifBlank { downloadedMessage }
|
unread = !markAsRead
|
||||||
}))
|
sender = new.sender
|
||||||
messageAttachmentDao.insertAttachments(attachments)
|
recipients = new.recipients.firstOrNull() ?: "Wielu adresoatów"
|
||||||
|
content = content.ifBlank { new.content }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
messageAttachmentDao.insertAttachments(
|
||||||
|
items = new.attachments.mapToEntities(message.messageGlobalKey),
|
||||||
|
)
|
||||||
|
|
||||||
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
|
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getMessagesFromDatabase(student: Student): Flow<List<Message>> {
|
fun getMessagesFromDatabase(mailbox: Mailbox): Flow<List<Message>> {
|
||||||
return messagesDb.loadAll(student.id.toInt(), RECEIVED.id)
|
return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateMessages(messages: List<Message>) {
|
suspend fun updateMessages(messages: List<Message>) {
|
||||||
@ -145,32 +123,48 @@ class MessageRepository @Inject constructor(
|
|||||||
subject: String,
|
subject: String,
|
||||||
content: String,
|
content: String,
|
||||||
recipients: List<Recipient>,
|
recipients: List<Recipient>,
|
||||||
): SentMessage = sdk.init(student).sendMessage(
|
mailboxId: String,
|
||||||
subject = subject,
|
) {
|
||||||
content = content,
|
sdk.init(student).sendMessage(
|
||||||
recipients = recipients.mapFromEntities()
|
subject = subject,
|
||||||
)
|
content = content,
|
||||||
|
recipients = recipients.mapFromEntities(),
|
||||||
|
mailboxId = mailboxId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List<Message>) {
|
||||||
val folderId = messages.first().folderId
|
val firstMessage = messages.first()
|
||||||
val isDeleted = sdk.init(student)
|
sdk.init(student).deleteMessages(
|
||||||
.deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
|
messages = messages.map { it.messageGlobalKey },
|
||||||
|
removeForever = firstMessage.folderId == TRASHED.id,
|
||||||
|
)
|
||||||
|
|
||||||
if (folderId != MessageFolder.TRASHED.id && isDeleted) {
|
if (firstMessage.folderId != TRASHED.id) {
|
||||||
val deletedMessages = messages.map {
|
val deletedMessages = messages.map {
|
||||||
it.copy(folderId = MessageFolder.TRASHED.id)
|
it.copy(folderId = TRASHED.id)
|
||||||
.apply {
|
.apply {
|
||||||
id = it.id
|
id = it.id
|
||||||
content = it.content
|
content = it.content
|
||||||
|
sender = it.sender
|
||||||
|
recipients = it.recipients
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messagesDb.updateAll(deletedMessages)
|
messagesDb.updateAll(deletedMessages)
|
||||||
} else messagesDb.deleteAll(messages)
|
} else messagesDb.deleteAll(messages)
|
||||||
|
|
||||||
|
getMessages(
|
||||||
|
student = student,
|
||||||
|
mailbox = mailbox,
|
||||||
|
folder = TRASHED,
|
||||||
|
forceRefresh = true,
|
||||||
|
).first()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteMessage(student: Student, message: Message) =
|
suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
|
||||||
deleteMessages(student, listOf(message))
|
deleteMessages(student, mailbox, listOf(message))
|
||||||
|
}
|
||||||
|
|
||||||
var draftMessage: MessageDraft?
|
var draftMessage: MessageDraft?
|
||||||
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
||||||
|
@ -39,12 +39,12 @@ class MobileDeviceRepository @Inject constructor(
|
|||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
||||||
.getRegisteredDevices()
|
.getRegisteredDevices()
|
||||||
.mapToEntities(semester)
|
.mapToEntities(student)
|
||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
mobileDb.deleteAll(old uniqueSubtract new)
|
mobileDb.deleteAll(old uniqueSubtract new)
|
||||||
|
@ -222,19 +222,31 @@ class PreferencesRepository @Inject constructor(
|
|||||||
get() = selectedDashboardTilesPreference.asFlow()
|
get() = selectedDashboardTilesPreference.asFlow()
|
||||||
.map { set ->
|
.map { set ->
|
||||||
set.map { DashboardItem.Tile.valueOf(it) }
|
set.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(
|
||||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
listOfNotNull(
|
||||||
|
DashboardItem.Tile.ACCOUNT,
|
||||||
|
DashboardItem.Tile.ADMIN_MESSAGE,
|
||||||
|
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
|
||||||
|
)
|
||||||
|
)
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedDashboardTiles: Set<DashboardItem.Tile>
|
var selectedDashboardTiles: Set<DashboardItem.Tile>
|
||||||
get() = selectedDashboardTilesPreference.get()
|
get() = selectedDashboardTilesPreference.get()
|
||||||
.map { DashboardItem.Tile.valueOf(it) }
|
.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(
|
||||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
listOfNotNull(
|
||||||
|
DashboardItem.Tile.ACCOUNT,
|
||||||
|
DashboardItem.Tile.ADMIN_MESSAGE,
|
||||||
|
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
|
||||||
|
)
|
||||||
|
)
|
||||||
.toSet()
|
.toSet()
|
||||||
set(value) {
|
set(value) {
|
||||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
val filteredValue = value.filterNot {
|
||||||
|
it == DashboardItem.Tile.ACCOUNT || it == DashboardItem.Tile.ADMIN_MESSAGE
|
||||||
|
}
|
||||||
.map { it.name }
|
.map { it.name }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
@ -271,7 +283,38 @@ class PreferencesRepository @Inject constructor(
|
|||||||
|
|
||||||
var isAppReviewDone: Boolean
|
var isAppReviewDone: Boolean
|
||||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||||
set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply()
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
|
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
|
||||||
|
|
||||||
@ -301,6 +344,10 @@ class PreferencesRepository @Inject constructor(
|
|||||||
|
|
||||||
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
|
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"
|
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.*
|
||||||
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.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
@ -23,9 +20,10 @@ class RecipientRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "recipient"
|
private val cacheKey = "recipient"
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
|
||||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
val new = sdk.init(student).getRecipients(mailbox.globalKey)
|
||||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
.mapToEntities(mailbox.globalKey)
|
||||||
|
val old = recipientDb.loadAll(type, mailbox.globalKey)
|
||||||
|
|
||||||
recipientDb.deleteAll(old uniqueSubtract new)
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
recipientDb.insertAll(new uniqueSubtract old)
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
@ -33,18 +31,27 @@ class RecipientRepository @Inject constructor(
|
|||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
suspend fun getRecipients(
|
||||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
student: Student,
|
||||||
|
mailbox: Mailbox,
|
||||||
|
type: MailboxType
|
||||||
|
): List<Recipient> {
|
||||||
|
val cached = recipientDb.loadAll(type, mailbox.globalKey)
|
||||||
|
|
||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
return if (cached.isEmpty() || isExpired) {
|
return if (cached.isEmpty() || isExpired) {
|
||||||
refreshRecipients(student, unit, role)
|
refreshRecipients(student, mailbox, type)
|
||||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
recipientDb.loadAll(type, mailbox.globalKey)
|
||||||
} else cached
|
} else cached
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
suspend fun getMessageSender(
|
||||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
student: Student,
|
||||||
.mapToEntities(student.studentId)
|
mailbox: Mailbox,
|
||||||
}
|
message: Message
|
||||||
|
): List<Recipient> = sdk.init(student)
|
||||||
|
.getMessageReplayDetails(message.messageGlobalKey)
|
||||||
|
.sender
|
||||||
|
.let(::listOf)
|
||||||
|
.mapToEntities(mailbox.globalKey)
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,7 +28,8 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
|
|
||||||
fun getSchoolAnnouncements(
|
fun getSchoolAnnouncements(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean, notify: Boolean = false
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
isResultEmpty = { it.isEmpty() },
|
isResultEmpty = { it.isEmpty() },
|
||||||
@ -37,7 +38,7 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(student.studentId)
|
schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
@ -56,7 +57,7 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
@ -15,79 +15,41 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
|
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
|
||||||
|
|
||||||
// Destination cannot be used here as shortcuts
|
fun initializeShortcuts() {
|
||||||
// require their intents to only use primitive types (see PersistableBundle.isValidType).
|
|
||||||
|
|
||||||
private val destinations = mapOf(
|
|
||||||
"grade" to Destination.Grade,
|
|
||||||
"attendance" to Destination.Attendance,
|
|
||||||
"exam" to Destination.Exam,
|
|
||||||
"timetable" to Destination.Timetable()
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
|
||||||
initializeShortcuts()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDestination(intent: Intent) =
|
|
||||||
destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)]
|
|
||||||
|
|
||||||
private fun initializeShortcuts() {
|
|
||||||
val shortcutsInfo = listOf(
|
val shortcutsInfo = listOf(
|
||||||
ShortcutInfoCompat.Builder(context, "grade_shortcut")
|
ShortcutInfoCompat.Builder(context, "grade_shortcut")
|
||||||
.setShortLabel(context.getString(R.string.grade_title))
|
.setShortLabel(context.getString(R.string.grade_title))
|
||||||
.setLongLabel(context.getString(R.string.grade_title))
|
.setLongLabel(context.getString(R.string.grade_title))
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
.setIntent(SplashActivity.getStartIntent(context, Destination.Grade)
|
||||||
.apply {
|
.apply { action = Intent.ACTION_VIEW })
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "attendance_shortcut")
|
ShortcutInfoCompat.Builder(context, "attendance_shortcut")
|
||||||
.setShortLabel(context.getString(R.string.attendance_title))
|
.setShortLabel(context.getString(R.string.attendance_title))
|
||||||
.setLongLabel(context.getString(R.string.attendance_title))
|
.setLongLabel(context.getString(R.string.attendance_title))
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
.setIntent(SplashActivity.getStartIntent(context, Destination.Attendance)
|
||||||
.apply {
|
.apply { action = Intent.ACTION_VIEW })
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "exam_shortcut")
|
ShortcutInfoCompat.Builder(context, "exam_shortcut")
|
||||||
.setShortLabel(context.getString(R.string.exam_title))
|
.setShortLabel(context.getString(R.string.exam_title))
|
||||||
.setLongLabel(context.getString(R.string.exam_title))
|
.setLongLabel(context.getString(R.string.exam_title))
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
.setIntent(SplashActivity.getStartIntent(context, Destination.Exam)
|
||||||
.apply {
|
.apply { action = Intent.ACTION_VIEW })
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ShortcutInfoCompat.Builder(context, "timetable_shortcut")
|
ShortcutInfoCompat.Builder(context, "timetable_shortcut")
|
||||||
.setShortLabel(context.getString(R.string.timetable_title))
|
.setShortLabel(context.getString(R.string.timetable_title))
|
||||||
.setLongLabel(context.getString(R.string.timetable_title))
|
.setLongLabel(context.getString(R.string.timetable_title))
|
||||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
|
||||||
.setIntent(SplashActivity.getStartIntent(context)
|
.setIntent(SplashActivity.getStartIntent(context, Destination.Timetable())
|
||||||
.apply {
|
.apply { action = Intent.ACTION_VIEW })
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
|
shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private companion object {
|
|
||||||
|
|
||||||
private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,7 @@ class NewMessageNotification @Inject constructor(
|
|||||||
val notificationDataList = items.map {
|
val notificationDataList = items.map {
|
||||||
NotificationData(
|
NotificationData(
|
||||||
title = context.getPlural(R.plurals.message_new_items, 1),
|
title = context.getPlural(R.plurals.message_new_items, 1),
|
||||||
content = "${it.sender}: ${it.subject}",
|
content = "${it.correspondents}: ${it.subject}",
|
||||||
destination = Destination.Message,
|
destination = Destination.Message,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
|||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||||
|
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
|
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
|
||||||
@ -11,19 +12,21 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class MessageWork @Inject constructor(
|
class MessageWork @Inject constructor(
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val newMessageNotification: NewMessageNotification,
|
private val newMessageNotification: NewMessageNotification,
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
messageRepository.getMessages(
|
messageRepository.getMessages(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
mailbox = mailbox,
|
||||||
folder = RECEIVED,
|
folder = RECEIVED,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = notify
|
notify = notify
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
messageRepository.getMessagesFromDatabase(student).first()
|
messageRepository.getMessagesFromDatabase(mailbox).first()
|
||||||
.filter { !it.isNotified && it.unread }.let {
|
.filter { !it.isNotified && it.unread }.let {
|
||||||
if (it.isNotEmpty()) newMessageNotification.notify(it, student)
|
if (it.isNotEmpty()) newMessageNotification.notify(it, student)
|
||||||
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })
|
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
package io.github.wulkanowy.services.sync.works
|
package io.github.wulkanowy.services.sync.works
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||||
import io.github.wulkanowy.data.repositories.RecipientRepository
|
import io.github.wulkanowy.data.repositories.RecipientRepository
|
||||||
import io.github.wulkanowy.data.repositories.ReportingUnitRepository
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RecipientWork @Inject constructor(
|
class RecipientWork @Inject constructor(
|
||||||
private val reportingUnitRepository: ReportingUnitRepository,
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val recipientRepository: RecipientRepository
|
private val recipientRepository: RecipientRepository
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||||
reportingUnitRepository.refreshReportingUnits(student)
|
mailboxRepository.refreshMailboxes(student)
|
||||||
|
|
||||||
reportingUnitRepository.getReportingUnits(student).let { units ->
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
units.map {
|
|
||||||
recipientRepository.refreshRecipients(student, it, 2)
|
recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
|
|||||||
import io.github.wulkanowy.data.waitForResult
|
import io.github.wulkanowy.data.waitForResult
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
|
import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SchoolAnnouncementWork @Inject constructor(
|
class SchoolAnnouncementWork @Inject constructor(
|
||||||
@ -20,10 +21,13 @@ class SchoolAnnouncementWork @Inject constructor(
|
|||||||
notify = notify,
|
notify = notify,
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
|
schoolAnnouncementRepository.getSchoolAnnouncementFromDatabase(student)
|
||||||
schoolAnnouncementRepository.getSchoolAnnouncementFromDatabase(student).first()
|
.first()
|
||||||
.filter { !it.isNotified }.let {
|
.filter { !it.isNotified && it.date >= LocalDate.now() }
|
||||||
if (it.isNotEmpty()) newSchoolAnnouncementNotification.notify(it, student)
|
.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
newSchoolAnnouncementNotification.notify(it, student)
|
||||||
|
}
|
||||||
|
|
||||||
schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement ->
|
schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement ->
|
||||||
schoolAnnouncement.isNotified = true
|
schoolAnnouncement.isNotified = true
|
||||||
|
@ -19,15 +19,15 @@ import kotlinx.serialization.Serializable
|
|||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class Destination private constructor() : java.io.Serializable {
|
sealed class Destination {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Type in children classes have to be as getter to avoid null in enums
|
Type in children classes have to be as getter to avoid null in enums
|
||||||
https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
|
https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
|
||||||
*/
|
*/
|
||||||
abstract val type: Type
|
abstract val destinationType: Type
|
||||||
|
|
||||||
abstract val fragment: Fragment
|
abstract val destinationFragment: Fragment
|
||||||
|
|
||||||
enum class Type(val defaultDestination: Destination) {
|
enum class Type(val defaultDestination: Destination) {
|
||||||
DASHBOARD(Dashboard),
|
DASHBOARD(Dashboard),
|
||||||
@ -47,26 +47,26 @@ sealed class Destination private constructor() : java.io.Serializable {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Dashboard : Destination() {
|
object Dashboard : Destination() {
|
||||||
override val type get() = Type.DASHBOARD
|
override val destinationType get() = Type.DASHBOARD
|
||||||
override val fragment get() = DashboardFragment.newInstance()
|
override val destinationFragment get() = DashboardFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Grade : Destination() {
|
object Grade : Destination() {
|
||||||
override val type get() = Type.GRADE
|
override val destinationType get() = Type.GRADE
|
||||||
override val fragment get() = GradeFragment.newInstance()
|
override val destinationFragment get() = GradeFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Attendance : Destination() {
|
object Attendance : Destination() {
|
||||||
override val type get() = Type.ATTENDANCE
|
override val destinationType get() = Type.ATTENDANCE
|
||||||
override val fragment get() = AttendanceFragment.newInstance()
|
override val destinationFragment get() = AttendanceFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Exam : Destination() {
|
object Exam : Destination() {
|
||||||
override val type get() = Type.EXAM
|
override val destinationType get() = Type.EXAM
|
||||||
override val fragment get() = ExamFragment.newInstance()
|
override val destinationFragment get() = ExamFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -74,55 +74,55 @@ sealed class Destination private constructor() : java.io.Serializable {
|
|||||||
@Serializable(with = LocalDateSerializer::class)
|
@Serializable(with = LocalDateSerializer::class)
|
||||||
private val date: LocalDate? = null
|
private val date: LocalDate? = null
|
||||||
) : Destination() {
|
) : Destination() {
|
||||||
override val type get() = Type.TIMETABLE
|
override val destinationType get() = Type.TIMETABLE
|
||||||
override val fragment get() = TimetableFragment.newInstance(date)
|
override val destinationFragment get() = TimetableFragment.newInstance(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Homework : Destination() {
|
object Homework : Destination() {
|
||||||
override val type get() = Type.HOMEWORK
|
override val destinationType get() = Type.HOMEWORK
|
||||||
override val fragment get() = HomeworkFragment.newInstance()
|
override val destinationFragment get() = HomeworkFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Note : Destination() {
|
object Note : Destination() {
|
||||||
override val type get() = Type.NOTE
|
override val destinationType get() = Type.NOTE
|
||||||
override val fragment get() = NoteFragment.newInstance()
|
override val destinationFragment get() = NoteFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Conference : Destination() {
|
object Conference : Destination() {
|
||||||
override val type get() = Type.CONFERENCE
|
override val destinationType get() = Type.CONFERENCE
|
||||||
override val fragment get() = ConferenceFragment.newInstance()
|
override val destinationFragment get() = ConferenceFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object SchoolAnnouncement : Destination() {
|
object SchoolAnnouncement : Destination() {
|
||||||
override val type get() = Type.SCHOOL_ANNOUNCEMENT
|
override val destinationType get() = Type.SCHOOL_ANNOUNCEMENT
|
||||||
override val fragment get() = SchoolAnnouncementFragment.newInstance()
|
override val destinationFragment get() = SchoolAnnouncementFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object School : Destination() {
|
object School : Destination() {
|
||||||
override val type get() = Type.SCHOOL
|
override val destinationType get() = Type.SCHOOL
|
||||||
override val fragment get() = SchoolFragment.newInstance()
|
override val destinationFragment get() = SchoolFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object LuckyNumber : Destination() {
|
object LuckyNumber : Destination() {
|
||||||
override val type get() = Type.LUCKY_NUMBER
|
override val destinationType get() = Type.LUCKY_NUMBER
|
||||||
override val fragment get() = LuckyNumberFragment.newInstance()
|
override val destinationFragment get() = LuckyNumberFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object More : Destination() {
|
object More : Destination() {
|
||||||
override val type get() = Type.MORE
|
override val destinationType get() = Type.MORE
|
||||||
override val fragment get() = MoreFragment.newInstance()
|
override val destinationFragment get() = MoreFragment.newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Message : Destination() {
|
object Message : Destination() {
|
||||||
override val type get() = Type.MESSAGE
|
override val destinationType get() = Type.MESSAGE
|
||||||
override val fragment get() = MessageFragment.newInstance()
|
override val destinationFragment get() = MessageFragment.newInstance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ class LicenseAdapter @Inject constructor() : RecyclerView.Adapter<LicenseAdapter
|
|||||||
val item = items[position]
|
val item = items[position]
|
||||||
|
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
licenseItemName.text = item.libraryName
|
licenseItemName.text = item.name
|
||||||
licenseItemSummary.text = item.licenses?.firstOrNull()?.licenseName?.takeIf { it.isNotBlank() } ?: item.libraryVersion
|
licenseItemSummary.text = item.licenses.firstOrNull()?.name?.takeIf { it.isNotBlank() }
|
||||||
|
?: item.artifactVersion
|
||||||
|
|
||||||
root.setOnClickListener { onClickListener(item) }
|
root.setOnClickListener { onClickListener(item) }
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.mikepenz.aboutlibraries.Libs
|
import com.mikepenz.aboutlibraries.Libs
|
||||||
import com.mikepenz.aboutlibraries.entity.Library
|
import com.mikepenz.aboutlibraries.entity.Library
|
||||||
|
import com.mikepenz.aboutlibraries.util.withContext
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.databinding.FragmentLicenseBinding
|
import io.github.wulkanowy.databinding.FragmentLicenseBinding
|
||||||
@ -28,7 +29,9 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.license_title
|
override val titleStringId get() = R.string.license_title
|
||||||
|
|
||||||
override val appLibraries by lazy { Libs(requireContext()).libraries }
|
override val appLibraries by lazy {
|
||||||
|
Libs.Builder().withContext(requireContext()).build().libraries
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = LicenseFragment()
|
fun newInstance() = LicenseFragment()
|
||||||
|
@ -22,7 +22,7 @@ class LicensePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(library: Library) {
|
fun onItemSelected(library: Library) {
|
||||||
view?.run { library.licenses?.firstOrNull()?.licenseDescription?.let { openLicense(it) } }
|
view?.run { library.licenses.firstOrNull()?.licenseContent?.let { openLicense(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
|
@ -35,9 +35,11 @@ class AttendanceAdapter @Inject constructor() :
|
|||||||
|
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
attendanceItemNumber.text = item.number.toString()
|
attendanceItemNumber.text = item.number.toString()
|
||||||
attendanceItemSubject.text = item.subject
|
attendanceItemSubject.text = item.subject.ifBlank {
|
||||||
|
root.context.getString(R.string.all_no_data)
|
||||||
|
}
|
||||||
attendanceItemDescription.setText(item.descriptionRes)
|
attendanceItemDescription.setText(item.descriptionRes)
|
||||||
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
|
attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
|
||||||
attendanceItemNumber.visibility = View.GONE
|
attendanceItemNumber.visibility = View.GONE
|
||||||
attendanceItemExcuseInfo.visibility = View.GONE
|
attendanceItemExcuseInfo.visibility = View.GONE
|
||||||
attendanceItemExcuseCheckbox.visibility = View.GONE
|
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||||
@ -46,7 +48,7 @@ class AttendanceAdapter @Inject constructor() :
|
|||||||
onExcuseCheckboxSelect(item, checked)
|
onExcuseCheckboxSelect(item, checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it)}) {
|
when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it) }) {
|
||||||
SentExcuseStatus.WAITING -> {
|
SentExcuseStatus.WAITING -> {
|
||||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
||||||
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
|
@ -91,15 +91,19 @@ class AttendancePresenter @Inject constructor(
|
|||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
Timber.i("Attendance view is reselected")
|
Timber.i("Attendance view is reselected")
|
||||||
view?.also { view ->
|
view?.let { view ->
|
||||||
if (view.currentStackSize == 1) {
|
if (view.currentStackSize == 1) {
|
||||||
baseDate.also {
|
baseDate = now().previousOrSameSchoolDay
|
||||||
if (currentDate != it) {
|
|
||||||
reloadView(it)
|
if (currentDate != baseDate) {
|
||||||
loadData()
|
reloadView(baseDate)
|
||||||
} else if (!view.isViewEmpty) view.resetView()
|
loadData()
|
||||||
|
} else if (!view.isViewEmpty) {
|
||||||
|
view.resetView()
|
||||||
}
|
}
|
||||||
} else view.popView()
|
} else {
|
||||||
|
view.popView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
|||||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
|
||||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
@ -47,6 +48,14 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
override var subtitleString =
|
override var subtitleString =
|
||||||
LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise()
|
LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise()
|
||||||
|
|
||||||
|
override val tileWidth: Int
|
||||||
|
get() {
|
||||||
|
val recyclerWidth = binding.dashboardRecycler.width
|
||||||
|
val margin = requireContext().dpToPx(24f).toInt()
|
||||||
|
|
||||||
|
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance() = DashboardFragment()
|
fun newInstance() = DashboardFragment()
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
import io.github.wulkanowy.data.db.entities.*
|
||||||
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.SchoolAnnouncement
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
import io.github.wulkanowy.data.pojos.TimetableFull
|
||||||
|
import io.github.wulkanowy.utils.AdBanner
|
||||||
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
|
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
|
||||||
|
|
||||||
sealed class DashboardItem(val type: Type) {
|
sealed class DashboardItem(val type: Type) {
|
||||||
@ -106,17 +102,26 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
override val isDataLoaded get() = conferences != null
|
override val isDataLoaded get() = conferences != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Ads(
|
||||||
|
val adBanner: AdBanner? = null,
|
||||||
|
override val error: Throwable? = null,
|
||||||
|
override val isLoading: Boolean = false
|
||||||
|
) : DashboardItem(Type.ADS) {
|
||||||
|
|
||||||
|
override val isDataLoaded get() = adBanner != null
|
||||||
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
ADMIN_MESSAGE,
|
ADMIN_MESSAGE,
|
||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
HORIZONTAL_GROUP,
|
HORIZONTAL_GROUP,
|
||||||
LESSONS,
|
LESSONS,
|
||||||
|
ADS,
|
||||||
GRADES,
|
GRADES,
|
||||||
HOMEWORK,
|
HOMEWORK,
|
||||||
ANNOUNCEMENTS,
|
ANNOUNCEMENTS,
|
||||||
EXAMS,
|
EXAMS,
|
||||||
CONFERENCES,
|
CONFERENCES,
|
||||||
ADS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Tile {
|
enum class Tile {
|
||||||
@ -126,12 +131,12 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
MESSAGES,
|
MESSAGES,
|
||||||
ATTENDANCE,
|
ATTENDANCE,
|
||||||
LESSONS,
|
LESSONS,
|
||||||
|
ADS,
|
||||||
GRADES,
|
GRADES,
|
||||||
HOMEWORK,
|
HOMEWORK,
|
||||||
ANNOUNCEMENTS,
|
ANNOUNCEMENTS,
|
||||||
EXAMS,
|
EXAMS,
|
||||||
CONFERENCES,
|
CONFERENCES,
|
||||||
ADS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,4 +153,4 @@ fun DashboardItem.Tile.toDashboardItemType() = when (this) {
|
|||||||
DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS
|
DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS
|
||||||
DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES
|
DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES
|
||||||
DashboardItem.Tile.ADS -> DashboardItem.Type.ADS
|
DashboardItem.Tile.ADS -> DashboardItem.Type.ADS
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard
|
|||||||
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import java.util.Collections
|
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class DashboardItemMoveCallback(
|
class DashboardItemMoveCallback(
|
||||||
private val dashboardAdapter: DashboardAdapter,
|
private val dashboardAdapter: DashboardAdapter,
|
||||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.data.enums.MessageFolder
|
|||||||
import io.github.wulkanowy.data.repositories.*
|
import io.github.wulkanowy.data.repositories.*
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
|
import io.github.wulkanowy.utils.AdsHelper
|
||||||
import io.github.wulkanowy.utils.calculatePercentage
|
import io.github.wulkanowy.utils.calculatePercentage
|
||||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@ -24,6 +25,7 @@ class DashboardPresenter @Inject constructor(
|
|||||||
private val gradeRepository: GradeRepository,
|
private val gradeRepository: GradeRepository,
|
||||||
private val semesterRepository: SemesterRepository,
|
private val semesterRepository: SemesterRepository,
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||||
private val timetableRepository: TimetableRepository,
|
private val timetableRepository: TimetableRepository,
|
||||||
private val homeworkRepository: HomeworkRepository,
|
private val homeworkRepository: HomeworkRepository,
|
||||||
@ -31,7 +33,8 @@ class DashboardPresenter @Inject constructor(
|
|||||||
private val conferenceRepository: ConferenceRepository,
|
private val conferenceRepository: ConferenceRepository,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||||
private val adminMessageRepository: AdminMessageRepository
|
private val adminMessageRepository: AdminMessageRepository,
|
||||||
|
private val adsHelper: AdsHelper
|
||||||
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
||||||
@ -55,7 +58,11 @@ class DashboardPresenter @Inject constructor(
|
|||||||
showContent(false)
|
showContent(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
preferencesRepository.selectedDashboardTilesFlow
|
merge(
|
||||||
|
preferencesRepository.selectedDashboardTilesFlow,
|
||||||
|
preferencesRepository.isAdsEnabledFlow
|
||||||
|
.map { preferencesRepository.selectedDashboardTiles }
|
||||||
|
)
|
||||||
.onEach { loadData(tilesToLoad = it) }
|
.onEach { loadData(tilesToLoad = it) }
|
||||||
.launch("dashboard_pref")
|
.launch("dashboard_pref")
|
||||||
}
|
}
|
||||||
@ -166,7 +173,7 @@ class DashboardPresenter @Inject constructor(
|
|||||||
DashboardItem.Type.CONFERENCES -> {
|
DashboardItem.Type.CONFERENCES -> {
|
||||||
loadConferences(student, forceRefresh)
|
loadConferences(student, forceRefresh)
|
||||||
}
|
}
|
||||||
DashboardItem.Type.ADS -> TODO()
|
DashboardItem.Type.ADS -> loadAds(forceRefresh)
|
||||||
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,6 +228,7 @@ class DashboardPresenter @Inject constructor(
|
|||||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||||
flow {
|
flow {
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
||||||
|
|
||||||
val flowSuccess = flowOf(Resource.Success(null))
|
val flowSuccess = flowOf(Resource.Success(null))
|
||||||
@ -232,7 +240,7 @@ class DashboardPresenter @Inject constructor(
|
|||||||
|
|
||||||
val messageFLow = messageRepository.getMessages(
|
val messageFLow = messageRepository.getMessages(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
mailbox = mailbox,
|
||||||
folder = MessageFolder.RECEIVED,
|
folder = MessageFolder.RECEIVED,
|
||||||
forceRefresh = forceRefresh
|
forceRefresh = forceRefresh
|
||||||
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
||||||
@ -595,6 +603,23 @@ class DashboardPresenter @Inject constructor(
|
|||||||
.launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh)
|
.launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadAds(forceRefresh: Boolean) {
|
||||||
|
presenterScope.launch {
|
||||||
|
if (!forceRefresh) {
|
||||||
|
updateData(DashboardItem.Ads(), forceRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dashboardAdItem =
|
||||||
|
runCatching {
|
||||||
|
DashboardItem.Ads(adsHelper.getDashboardTileAdBanner(view!!.tileWidth))
|
||||||
|
}
|
||||||
|
.onFailure { Timber.e(it) }
|
||||||
|
.getOrElse { DashboardItem.Ads(error = it) }
|
||||||
|
|
||||||
|
updateData(dashboardAdItem, forceRefresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||||
val isFirstRunDataLoadedError =
|
val isFirstRunDataLoadedError =
|
||||||
@ -619,6 +644,18 @@ class DashboardPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dashboardItem is DashboardItem.Ads) {
|
||||||
|
if (!dashboardItem.isDataLoaded) {
|
||||||
|
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADS
|
||||||
|
dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADS
|
||||||
|
|
||||||
|
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADS }
|
||||||
|
} else {
|
||||||
|
dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADS
|
||||||
|
dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (forceRefresh) {
|
if (forceRefresh) {
|
||||||
updateForceRefreshData(dashboardItem)
|
updateForceRefreshData(dashboardItem)
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
|
|||||||
|
|
||||||
interface DashboardView : BaseView {
|
interface DashboardView : BaseView {
|
||||||
|
|
||||||
|
val tileWidth: Int
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<DashboardItem>)
|
fun updateData(data: List<DashboardItem>)
|
||||||
@ -27,4 +29,4 @@ interface DashboardView : BaseView {
|
|||||||
fun openNotificationsCenterView()
|
fun openNotificationsCenterView()
|
||||||
|
|
||||||
fun openInternetBrowser(url: String)
|
fun openInternetBrowser(url: String)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
@ -9,6 +9,7 @@ import android.os.Looper
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updateMarginsRelative
|
import androidx.core.view.updateMarginsRelative
|
||||||
@ -21,24 +22,15 @@ import io.github.wulkanowy.data.db.entities.Student
|
|||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
import io.github.wulkanowy.databinding.*
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
|
import io.github.wulkanowy.utils.*
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardGradesBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardHomeworkBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardHorizontalGroupBinding
|
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardLessonsBinding
|
|
||||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
|
||||||
import io.github.wulkanowy.utils.left
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.*
|
import java.time.Duration
|
||||||
import java.util.Timer
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.concurrent.timer
|
import kotlin.concurrent.timer
|
||||||
|
|
||||||
@ -119,6 +111,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
|
||||||
|
ItemDashboardAdsBinding.inflate(inflater, parent, false)
|
||||||
|
)
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,6 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||||
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
||||||
is AdminMessageViewHolder -> bindAdminMessage(holder, position)
|
is AdminMessageViewHolder -> bindAdminMessage(holder, position)
|
||||||
|
is AdsViewHolder -> bindAdsViewHolder(holder, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,7 +559,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
) {
|
) {
|
||||||
with(binding.dashboardLessonsItemDayHeader) {
|
with(binding.dashboardLessonsItemDayHeader) {
|
||||||
isVisible = header != null
|
isVisible = header != null
|
||||||
text = header?.content
|
text = header?.content?.parseAsHtml()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,6 +741,20 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) {
|
||||||
|
val item = (items[position] as DashboardItem.Ads).adBanner ?: return
|
||||||
|
val binding = adsViewHolder.binding
|
||||||
|
|
||||||
|
binding.dashboardAdminMessageItemContent.removeAllViews()
|
||||||
|
binding.dashboardAdminMessageItemContent.addView(
|
||||||
|
item.view,
|
||||||
|
ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
|
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
@ -787,6 +797,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
class AdsViewHolder(val binding: ItemDashboardAdsBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
private class DiffCallback(
|
private class DiffCallback(
|
||||||
private val newList: List<DashboardItem>,
|
private val newList: List<DashboardItem>,
|
||||||
private val oldList: List<DashboardItem>
|
private val oldList: List<DashboardItem>
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -33,4 +33,4 @@ class DashboardAnnouncementsAdapter :
|
|||||||
|
|
||||||
class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) :
|
class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -33,4 +33,4 @@ class DashboardConferencesAdapter :
|
|||||||
|
|
||||||
class ViewHolder(val binding: SubitemDashboardConferencesBinding) :
|
class ViewHolder(val binding: SubitemDashboardConferencesBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -56,4 +56,4 @@ class DashboardExamsAdapter :
|
|||||||
|
|
||||||
class ViewHolder(val binding: SubitemDashboardExamsBinding) :
|
class ViewHolder(val binding: SubitemDashboardExamsBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -53,4 +53,4 @@ class DashboardHomeworkAdapter : RecyclerView.Adapter<DashboardHomeworkAdapter.V
|
|||||||
|
|
||||||
class ViewHolder(val binding: SubitemDashboardHomeworkBinding) :
|
class ViewHolder(val binding: SubitemDashboardHomeworkBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -17,16 +17,13 @@ val debugMessageItems = listOf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun generateMessage(sender: String, subject: String) = Message(
|
private fun generateMessage(sender: String, subject: String) = Message(
|
||||||
sender = sender,
|
|
||||||
subject = subject,
|
subject = subject,
|
||||||
studentId = 0,
|
messageId = 123,
|
||||||
realId = 0,
|
|
||||||
messageId = 0,
|
|
||||||
senderId = 0,
|
|
||||||
recipient = "",
|
|
||||||
date = Instant.now(),
|
date = Instant.now(),
|
||||||
folderId = 0,
|
folderId = 0,
|
||||||
unread = true,
|
unread = true,
|
||||||
removed = false,
|
hasAttachments = false,
|
||||||
hasAttachments = false
|
messageGlobalKey = "",
|
||||||
|
correspondents = sender,
|
||||||
|
mailboxKey = "",
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf(
|
|||||||
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
studentId = 0,
|
userLoginId = 0,
|
||||||
date = LocalDate.now()
|
date = LocalDate.now()
|
||||||
)
|
)
|
||||||
|
@ -3,8 +3,8 @@ package io.github.wulkanowy.ui.modules.grade.details
|
|||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.enums.GradeExpandMode
|
import io.github.wulkanowy.data.enums.GradeExpandMode
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
|
import io.github.wulkanowy.data.enums.GradeSortingMode
|
||||||
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
|
import io.github.wulkanowy.data.enums.GradeSortingMode.*
|
||||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
@ -204,6 +204,7 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
|
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
|
||||||
gradeDetailsWithAverage.subject.lowercase()
|
gradeDetailsWithAverage.subject.lowercase()
|
||||||
}
|
}
|
||||||
|
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { (subject, average, points, _, grades) ->
|
.map { (subject, average, points, _, grades) ->
|
||||||
|
@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.grade.summary
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
import io.github.wulkanowy.data.enums.GradeSortingMode
|
||||||
|
import io.github.wulkanowy.data.enums.GradeSortingMode.*
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
@ -14,6 +17,7 @@ import javax.inject.Inject
|
|||||||
class GradeSummaryPresenter @Inject constructor(
|
class GradeSummaryPresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val averageProvider: GradeAverageProvider,
|
private val averageProvider: GradeAverageProvider,
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository) {
|
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository) {
|
||||||
@ -127,7 +131,17 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||||
return items
|
return items
|
||||||
.filter { !checkEmpty(it) }
|
.filter { !checkEmpty(it) }
|
||||||
.sortedBy { it.subject }
|
.let { gradeSubjects ->
|
||||||
|
when (preferencesRepository.gradeSortingMode) {
|
||||||
|
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
|
||||||
|
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
|
||||||
|
}
|
||||||
|
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
|
||||||
|
gradeDetailsWithAverage.subject.lowercase()
|
||||||
|
}
|
||||||
|
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
|
||||||
|
}
|
||||||
|
}
|
||||||
.map { it.summary.copy(average = it.average) }
|
.map { it.summary.copy(average = it.average) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
|||||||
}
|
}
|
||||||
|
|
||||||
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
@ -172,7 +172,7 @@ class LoginFormPresenter @Inject constructor(
|
|||||||
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
|
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
|
||||||
val emailHost = login.substringAfter("@")
|
val emailHost = login.substringAfter("@")
|
||||||
val emailDomain = URL(host).host
|
val emailDomain = URL(host).host
|
||||||
if (emailHost != emailDomain) {
|
if (!emailHost.equals(emailDomain, true)) {
|
||||||
view?.setErrorEmailInvalid(domain = emailDomain)
|
view?.setErrorEmailInvalid(domain = emailDomain)
|
||||||
isCorrect = false
|
isCorrect = false
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.*
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
@ -206,10 +204,9 @@ class LoginRecoverFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceivedError(
|
override fun onReceivedError(
|
||||||
view: WebView,
|
view: WebView?,
|
||||||
errorCode: Int,
|
request: WebResourceRequest?,
|
||||||
description: String,
|
error: WebResourceError?
|
||||||
failingUrl: String
|
|
||||||
) {
|
) {
|
||||||
recoverWebViewSuccess = false
|
recoverWebViewSuccess = false
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
|||||||
.apply {
|
.apply {
|
||||||
setTextViewText(
|
setTextViewText(
|
||||||
R.id.luckyNumberWidgetNumber,
|
R.id.luckyNumberWidgetNumber,
|
||||||
luckyNumber.dataOrNull?.toString() ?: "#"
|
luckyNumber.dataOrNull?.luckyNumber?.toString() ?: "#"
|
||||||
)
|
)
|
||||||
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
|
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
import com.google.android.material.elevation.ElevationOverlayProvider
|
||||||
import com.ncapdevi.fragnav.FragNavController
|
import com.ncapdevi.fragnav.FragNavController
|
||||||
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
||||||
@ -20,10 +21,13 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.databinding.ActivityMainBinding
|
import io.github.wulkanowy.databinding.ActivityMainBinding
|
||||||
|
import io.github.wulkanowy.databinding.DialogAdsConsentBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.*
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -55,13 +59,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val EXTRA_START_DESTINATION = "start_destination"
|
private const val EXTRA_START_DESTINATION = "start_destination_json"
|
||||||
|
|
||||||
fun getStartIntent(
|
fun getStartIntent(
|
||||||
context: Context,
|
context: Context,
|
||||||
destination: Destination? = null,
|
destination: Destination? = null,
|
||||||
) = Intent(context, MainActivity::class.java).apply {
|
) = Intent(context, MainActivity::class.java).apply {
|
||||||
putExtra(EXTRA_START_DESTINATION, destination)
|
destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +74,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
override val currentStackSize get() = navController.currentStack?.size
|
override val currentStackSize get() = navController.currentStack?.size
|
||||||
|
|
||||||
override val currentViewTitle
|
override val currentViewTitle
|
||||||
get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let {
|
get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId
|
||||||
getString(it)
|
?.let { getString(it) }
|
||||||
}
|
|
||||||
|
|
||||||
override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
|
override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
|
||||||
|
|
||||||
@ -86,7 +89,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
messageContainer = binding.mainMessageContainer
|
messageContainer = binding.mainMessageContainer
|
||||||
updateHelper.messageContainer = binding.mainFragmentContainer
|
updateHelper.messageContainer = binding.mainFragmentContainer
|
||||||
|
|
||||||
val destination = (intent.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?)
|
val destination = intent.getStringExtra(EXTRA_START_DESTINATION)
|
||||||
?.takeIf { savedInstanceState == null }
|
?.takeIf { savedInstanceState == null }
|
||||||
|
|
||||||
presenter.onAttachView(this, destination)
|
presenter.onAttachView(this, destination)
|
||||||
@ -99,6 +102,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
@ -129,7 +133,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
fragmentHideStrategy = HIDE
|
fragmentHideStrategy = HIDE
|
||||||
rootFragments = rootDestinations.map { it.fragment }
|
rootFragments = rootDestinations.map { it.destinationFragment }
|
||||||
|
|
||||||
initialize(startMenuIndex, savedInstanceState)
|
initialize(startMenuIndex, savedInstanceState)
|
||||||
}
|
}
|
||||||
@ -230,7 +234,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun openMoreDestination(destination: Destination) {
|
override fun openMoreDestination(destination: Destination) {
|
||||||
pushView(destination.fragment)
|
pushView(destination.destinationFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyMenuViewReselected() {
|
override fun notifyMenuViewReselected() {
|
||||||
@ -286,6 +290,50 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
inAppReviewHelper.showInAppReview(this)
|
inAppReviewHelper.showInAppReview(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showAppSupport() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.main_support_title)
|
||||||
|
.setMessage(R.string.main_support_description)
|
||||||
|
.setPositiveButton(R.string.main_support_positive) { _, _ -> presenter.onEnableAdsSelected() }
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
.setOnDismissListener { }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showPrivacyPolicyDialog() {
|
||||||
|
val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
val dialog = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.pref_ads_consent_title)
|
||||||
|
.setMessage(R.string.pref_ads_consent_description)
|
||||||
|
.setView(dialogAdsConsentBinding.root)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
|
||||||
|
presenter.onPrivacyAgree(true)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
|
||||||
|
presenter.onPrivacyAgree(false)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
|
||||||
|
dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openPrivacyPolicy() {
|
||||||
|
openInternetBrowser(
|
||||||
|
"https://wulkanowy.github.io/polityka-prywatnosci.html",
|
||||||
|
::showMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navController.onSaveInstanceState(outState)
|
navController.onSaveInstanceState(outState)
|
||||||
|
@ -18,7 +18,12 @@ import io.github.wulkanowy.ui.modules.grade.GradeView
|
|||||||
import io.github.wulkanowy.ui.modules.message.MessageView
|
import io.github.wulkanowy.ui.modules.message.MessageView
|
||||||
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView
|
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView
|
||||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||||
|
import io.github.wulkanowy.utils.AdsHelper
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -27,9 +32,12 @@ import javax.inject.Inject
|
|||||||
class MainPresenter @Inject constructor(
|
class MainPresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val prefRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val syncManager: SyncManager,
|
private val syncManager: SyncManager,
|
||||||
private val analytics: AnalyticsHelper,
|
private val analytics: AnalyticsHelper,
|
||||||
|
private val json: Json,
|
||||||
|
private val adsHelper: AdsHelper,
|
||||||
|
private val appInfo: AppInfo
|
||||||
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private var studentsWitSemesters: List<StudentWithSemesters>? = null
|
private var studentsWitSemesters: List<StudentWithSemesters>? = null
|
||||||
@ -44,19 +52,21 @@ class MainPresenter @Inject constructor(
|
|||||||
|
|
||||||
private val Destination?.startMenuIndex
|
private val Destination?.startMenuIndex
|
||||||
get() = when {
|
get() = when {
|
||||||
this == null -> prefRepository.startMenuIndex
|
this == null -> preferencesRepository.startMenuIndex
|
||||||
type in rootDestinationTypeList -> {
|
destinationType in rootDestinationTypeList -> {
|
||||||
rootDestinationTypeList.indexOf(type)
|
rootDestinationTypeList.indexOf(destinationType)
|
||||||
}
|
}
|
||||||
else -> 4
|
else -> 4
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAttachView(view: MainView, initDestination: Destination?) {
|
fun onAttachView(view: MainView, initDestinationJson: String?) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
|
|
||||||
|
val initDestination: Destination? = initDestinationJson?.let { json.decodeFromString(it) }
|
||||||
|
|
||||||
val startMenuIndex = initDestination.startMenuIndex
|
val startMenuIndex = initDestination.startMenuIndex
|
||||||
val destinations = rootDestinationTypeList.map {
|
val destinations = rootDestinationTypeList.map {
|
||||||
if (it == initDestination?.type) initDestination else it.defaultDestination
|
if (it == initDestination?.destinationType) initDestination else it.defaultDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
view.initView(startMenuIndex, destinations)
|
view.initView(startMenuIndex, destinations)
|
||||||
@ -66,6 +76,8 @@ class MainPresenter @Inject constructor(
|
|||||||
|
|
||||||
syncManager.startPeriodicSyncWorker()
|
syncManager.startPeriodicSyncWorker()
|
||||||
|
|
||||||
|
checkAppSupport()
|
||||||
|
|
||||||
analytics.logEvent("app_open", "destination" to initDestination.toString())
|
analytics.logEvent("app_open", "destination" to initDestination.toString())
|
||||||
Timber.i("Main view was initialized with $initDestination")
|
Timber.i("Main view was initialized with $initDestination")
|
||||||
}
|
}
|
||||||
@ -150,18 +162,52 @@ class MainPresenter @Inject constructor(
|
|||||||
} == true
|
} == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkInAppReview() {
|
fun onEnableAdsSelected() {
|
||||||
prefRepository.inAppReviewCount++
|
view?.showPrivacyPolicyDialog()
|
||||||
|
}
|
||||||
|
|
||||||
if (prefRepository.inAppReviewDate == null) {
|
fun onPrivacyAgree(isPersonalizedAds: Boolean) {
|
||||||
prefRepository.inAppReviewDate = Instant.now()
|
preferencesRepository.isAgreeToProcessData = true
|
||||||
|
preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds
|
||||||
|
|
||||||
|
adsHelper.initialize()
|
||||||
|
|
||||||
|
preferencesRepository.isAdsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPrivacySelected() {
|
||||||
|
view?.openPrivacyPolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkInAppReview() {
|
||||||
|
preferencesRepository.inAppReviewCount++
|
||||||
|
|
||||||
|
if (preferencesRepository.inAppReviewDate == null) {
|
||||||
|
preferencesRepository.inAppReviewDate = Instant.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prefRepository.isAppReviewDone && prefRepository.inAppReviewCount >= 50 &&
|
if (!preferencesRepository.isAppReviewDone && preferencesRepository.inAppReviewCount >= 50 &&
|
||||||
Instant.now().minus(Duration.ofDays(14)).isAfter(prefRepository.inAppReviewDate)
|
Instant.now().minus(Duration.ofDays(14)).isAfter(preferencesRepository.inAppReviewDate)
|
||||||
) {
|
) {
|
||||||
view?.showInAppReview()
|
view?.showInAppReview()
|
||||||
prefRepository.isAppReviewDone = true
|
preferencesRepository.isAppReviewDone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAppSupport() {
|
||||||
|
if (!preferencesRepository.isAppSupportShown && !preferencesRepository.isAdsEnabled
|
||||||
|
&& appInfo.buildFlavor == "play"
|
||||||
|
) {
|
||||||
|
presenterScope.launch {
|
||||||
|
val student = runCatching { studentRepository.getCurrentStudent(false) }
|
||||||
|
.onFailure { Timber.e(it) }
|
||||||
|
.getOrElse { return@launch }
|
||||||
|
|
||||||
|
if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) {
|
||||||
|
view?.showAppSupport()
|
||||||
|
preferencesRepository.isAppSupportShown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,12 @@ interface MainView : BaseView {
|
|||||||
|
|
||||||
fun showInAppReview()
|
fun showInAppReview()
|
||||||
|
|
||||||
|
fun showAppSupport()
|
||||||
|
|
||||||
|
fun showPrivacyPolicyDialog()
|
||||||
|
|
||||||
|
fun openPrivacyPolicy()
|
||||||
|
|
||||||
fun openMoreDestination(destination: Destination)
|
fun openMoreDestination(destination: Destination)
|
||||||
|
|
||||||
interface MainChildView {
|
interface MainChildView {
|
||||||
|
@ -4,6 +4,8 @@ import android.annotation.SuppressLint
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
@ -75,29 +77,25 @@ class MessagePreviewAdapter @Inject constructor() :
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun bindMessage(holder: MessageViewHolder, message: Message) {
|
private fun bindMessage(holder: MessageViewHolder, message: Message) {
|
||||||
val context = holder.binding.root.context
|
val context = holder.binding.root.context
|
||||||
val recipientCount = message.unreadBy + message.readBy
|
|
||||||
|
|
||||||
val readText = when {
|
val readTextValue = when {
|
||||||
recipientCount > 1 -> {
|
!message.unread -> R.string.all_yes
|
||||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
else -> R.string.all_no
|
||||||
}
|
|
||||||
message.readBy == 1 -> {
|
|
||||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
|
||||||
}
|
|
||||||
else -> context.getString(R.string.message_read, context.getString(R.string.all_no))
|
|
||||||
}
|
}
|
||||||
|
val readText = context.getString(R.string.message_read, context.getString(readTextValue))
|
||||||
|
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
messagePreviewSubject.text =
|
messagePreviewSubject.text = message.subject.ifBlank {
|
||||||
message.subject.ifBlank { root.context.getString(R.string.message_no_subject) }
|
context.getString(R.string.message_no_subject)
|
||||||
messagePreviewDate.text = root.context.getString(
|
}
|
||||||
|
messagePreviewDate.text = context.getString(
|
||||||
R.string.message_date,
|
R.string.message_date,
|
||||||
message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
||||||
)
|
)
|
||||||
messagePreviewRead.text = readText
|
messagePreviewRead.text = readText
|
||||||
messagePreviewContent.text = message.content
|
messagePreviewContent.text = message.content.parseAsHtml(FROM_HTML_MODE_COMPACT)
|
||||||
messagePreviewFromSender.text = message.sender
|
messagePreviewFromSender.text = message.sender
|
||||||
messagePreviewToRecipient.text = message.recipient
|
messagePreviewToRecipient.text = message.recipients
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,8 @@ class MessagePreviewFragment :
|
|||||||
get() = getString(R.string.message_no_subject)
|
get() = getString(R.string.message_no_subject)
|
||||||
|
|
||||||
override val printHTML: String
|
override val printHTML: String
|
||||||
get() = requireContext().assets.open("message-print-page.html").bufferedReader().use { it.readText() }
|
get() = requireContext().assets.open("message-print-page.html").bufferedReader()
|
||||||
|
.use { it.readText() }
|
||||||
|
|
||||||
override val messageNotExists: String
|
override val messageNotExists: String
|
||||||
get() = getString(R.string.message_not_exists)
|
get() = getString(R.string.message_not_exists)
|
||||||
@ -81,7 +82,10 @@ class MessagePreviewFragment :
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentMessagePreviewBinding.bind(view)
|
binding = FragmentMessagePreviewBinding.bind(view)
|
||||||
messageContainer = binding.messagePreviewContainer
|
messageContainer = binding.messagePreviewContainer
|
||||||
presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message)
|
presenter.onAttachView(
|
||||||
|
this,
|
||||||
|
(savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
@ -101,6 +105,8 @@ class MessagePreviewFragment :
|
|||||||
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
||||||
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
||||||
presenter.onCreateOptionsMenu()
|
presenter.onCreateOptionsMenu()
|
||||||
|
|
||||||
|
menu.findItem(R.id.mainMenuAccount).isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@ -129,8 +135,8 @@ class MessagePreviewFragment :
|
|||||||
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showOptions(show: Boolean) {
|
override fun showOptions(show: Boolean, isReplayable: Boolean) {
|
||||||
menuReplyButton?.isVisible = show
|
menuReplyButton?.isVisible = isReplayable
|
||||||
menuForwardButton?.isVisible = show
|
menuForwardButton?.isVisible = show
|
||||||
menuDeleteButton?.isVisible = show
|
menuDeleteButton?.isVisible = show
|
||||||
menuShareButton?.isVisible = show
|
menuShareButton?.isVisible = show
|
||||||
@ -173,7 +179,8 @@ class MessagePreviewFragment :
|
|||||||
val webView = WebView(requireContext())
|
val webView = WebView(requireContext())
|
||||||
webView.webViewClient = object : WebViewClient() {
|
webView.webViewClient = object : WebViewClient() {
|
||||||
|
|
||||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false
|
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) =
|
||||||
|
false
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView, url: String) {
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
createWebPrintJob(view, jobName)
|
createWebPrintJob(view, jobName)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package io.github.wulkanowy.ui.modules.message.preview
|
package io.github.wulkanowy.ui.modules.message.preview
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
import io.github.wulkanowy.data.enums.MessageFolder
|
||||||
|
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -19,6 +21,7 @@ class MessagePreviewPresenter @Inject constructor(
|
|||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
@ -52,7 +55,7 @@ class MessagePreviewPresenter @Inject constructor(
|
|||||||
|
|
||||||
private fun loadData(messageToLoad: Message) {
|
private fun loadData(messageToLoad: Message) {
|
||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val student = studentRepository.getStudentById(messageToLoad.studentId)
|
val student = studentRepository.getCurrentStudent()
|
||||||
messageRepository.getMessage(student, messageToLoad, true)
|
messageRepository.getMessage(student, messageToLoad, true)
|
||||||
}
|
}
|
||||||
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
||||||
@ -104,62 +107,69 @@ class MessagePreviewPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onShare(): Boolean {
|
fun onShare(): Boolean {
|
||||||
message?.let {
|
val message = message ?: return false
|
||||||
var text =
|
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||||
"Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
|
|
||||||
true -> "Od: ${it.sender}\n"
|
|
||||||
false -> "Do: ${it.recipient}\n"
|
|
||||||
} + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
|
|
||||||
|
|
||||||
attachments?.let { attachments ->
|
val text = buildString {
|
||||||
if (attachments.isNotEmpty()) {
|
appendLine("Temat: $subject")
|
||||||
text += "\n\nZałączniki:"
|
appendLine("Od: ${message.sender}")
|
||||||
|
appendLine("Do: ${message.recipients}")
|
||||||
|
appendLine("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}")
|
||||||
|
|
||||||
attachments.forEach { attachment ->
|
appendLine()
|
||||||
text += "\n${attachment.filename}: ${attachment.url}"
|
|
||||||
}
|
appendLine(message.content.parseAsHtml())
|
||||||
}
|
|
||||||
|
if (!attachments.isNullOrEmpty()) {
|
||||||
|
appendLine()
|
||||||
|
appendLine("Załączniki:")
|
||||||
|
|
||||||
|
append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
|
||||||
|
"${attachment.filename}: ${attachment.url}"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
view?.shareText(
|
|
||||||
text,
|
|
||||||
"FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}"
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
view?.shareText(
|
||||||
|
subject = "FW: $subject",
|
||||||
|
text = text,
|
||||||
|
)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
fun onPrint(): Boolean {
|
fun onPrint(): Boolean {
|
||||||
message?.let {
|
val message = message ?: return false
|
||||||
val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||||
val infoContent = "<div><h4>Data wysłania</h4>$dateString</div>" + when {
|
|
||||||
it.sender.isNotEmpty() -> "<div><h4>Od</h4>${it.sender}</div>"
|
|
||||||
else -> "<div><h4>Do</h4>${it.recipient}</div>"
|
|
||||||
}
|
|
||||||
|
|
||||||
val messageContent = "<p>${it.content}</p>"
|
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
||||||
.replace(Regex("[\\n\\r]{2,}"), "</p><p>")
|
|
||||||
.replace(Regex("[\\n\\r]"), "<br>")
|
|
||||||
|
|
||||||
val jobName = "Wiadomość " + when {
|
val infoContent = buildString {
|
||||||
it.sender.isNotEmpty() -> "od ${it.sender}"
|
append("<div><h4>Data wysłania</h4>$dateString</div>")
|
||||||
else -> "do ${it.recipient}"
|
|
||||||
} + " $dateString: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }} | Wulkanowy"
|
|
||||||
|
|
||||||
view?.apply {
|
append("<div><h4>Od</h4>${message.sender}</div>")
|
||||||
val html = printHTML
|
append("<div><h4>DO</h4>${message.recipients}</div>")
|
||||||
.replace(
|
|
||||||
"%SUBJECT%",
|
|
||||||
it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
|
|
||||||
.replace("%CONTENT%", messageContent)
|
|
||||||
.replace("%INFO%", infoContent)
|
|
||||||
printDocument(html, jobName)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
val messageContent = "<p>${message.content}</p>"
|
||||||
|
.replace(Regex("[\\n\\r]{2,}"), "</p><p>")
|
||||||
|
.replace(Regex("[\\n\\r]"), "<br>")
|
||||||
|
|
||||||
|
val jobName = buildString {
|
||||||
|
append("Wiadomość ")
|
||||||
|
append("od ${message.correspondents}")
|
||||||
|
append("do ${message.correspondents}")
|
||||||
|
append(" $dateString: $subject | Wulkanowy")
|
||||||
|
}
|
||||||
|
|
||||||
|
view?.apply {
|
||||||
|
val html = printHTML
|
||||||
|
.replace("%SUBJECT%", subject)
|
||||||
|
.replace("%CONTENT%", messageContent)
|
||||||
|
.replace("%INFO%", infoContent)
|
||||||
|
printDocument(html, jobName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteMessage() {
|
private fun deleteMessage() {
|
||||||
@ -168,16 +178,17 @@ class MessagePreviewPresenter @Inject constructor(
|
|||||||
view?.run {
|
view?.run {
|
||||||
showContent(false)
|
showContent(false)
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
showOptions(false)
|
showOptions(show = false, isReplayable = false)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.i("Delete message ${message?.id}")
|
Timber.i("Delete message ${message?.messageGlobalKey}")
|
||||||
|
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||||
messageRepository.deleteMessage(student, message!!)
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
|
messageRepository.deleteMessage(student, mailbox, message!!)
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
retryCallback = { onMessageDelete() }
|
retryCallback = { onMessageDelete() }
|
||||||
@ -211,7 +222,10 @@ class MessagePreviewPresenter @Inject constructor(
|
|||||||
|
|
||||||
private fun initOptions() {
|
private fun initOptions() {
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showOptions(message != null)
|
showOptions(
|
||||||
|
show = message != null,
|
||||||
|
isReplayable = message?.folderId != MessageFolder.SENT.id,
|
||||||
|
)
|
||||||
message?.let {
|
message?.let {
|
||||||
when (it.folderId == MessageFolder.TRASHED.id) {
|
when (it.folderId == MessageFolder.TRASHED.id) {
|
||||||
true -> setDeletedOptionsLabels()
|
true -> setDeletedOptionsLabels()
|
||||||
|
@ -28,7 +28,7 @@ interface MessagePreviewView : BaseView {
|
|||||||
|
|
||||||
fun setErrorRetryCallback(callback: () -> Unit)
|
fun setErrorRetryCallback(callback: () -> Unit)
|
||||||
|
|
||||||
fun showOptions(show: Boolean)
|
fun showOptions(show: Boolean, isReplayable: Boolean)
|
||||||
|
|
||||||
fun setDeletedOptionsLabels()
|
fun setDeletedOptionsLabels()
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.Spanned
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.TouchDelegate
|
import android.view.TouchDelegate
|
||||||
@ -13,11 +14,12 @@ import android.view.View.GONE
|
|||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
|
import androidx.core.text.toHtml
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
|
||||||
import io.github.wulkanowy.databinding.ActivitySendMessageBinding
|
import io.github.wulkanowy.databinding.ActivitySendMessageBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
@ -72,17 +74,32 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
|||||||
override val messageSuccess: String
|
override val messageSuccess: String
|
||||||
get() = getString(R.string.message_send_successful)
|
get() = getString(R.string.message_send_successful)
|
||||||
|
|
||||||
|
override val mailboxStudent: String
|
||||||
|
get() = getString(R.string.message_mailbox_type_student)
|
||||||
|
|
||||||
|
override val mailboxParent: String
|
||||||
|
get() = getString(R.string.message_mailbox_type_parent)
|
||||||
|
|
||||||
|
override val mailboxGuardian: String
|
||||||
|
get() = getString(R.string.message_mailbox_type_guardian)
|
||||||
|
|
||||||
|
override val mailboxEmployee: String
|
||||||
|
get() = getString(R.string.message_mailbox_type_employee)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root)
|
setContentView(
|
||||||
|
ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root
|
||||||
|
)
|
||||||
setSupportActionBar(binding.sendMessageToolbar)
|
setSupportActionBar(binding.sendMessageToolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
messageContainer = binding.sendMessageContainer
|
messageContainer = binding.sendMessageContainer
|
||||||
|
|
||||||
formRecipientsData = binding.sendMessageTo.addedChipItems as List<RecipientChipItem>
|
formRecipientsData = binding.sendMessageTo.addedChipItems as List<RecipientChipItem>
|
||||||
formSubjectValue = binding.sendMessageSubject.text.toString()
|
formSubjectValue = binding.sendMessageSubject.text.toString()
|
||||||
formContentValue = binding.sendMessageMessageContent.text.toString()
|
formContentValue =
|
||||||
|
binding.sendMessageMessageContent.text.toString().parseAsHtml().toString()
|
||||||
|
|
||||||
presenter.onAttachView(
|
presenter.onAttachView(
|
||||||
view = this,
|
view = this,
|
||||||
@ -110,7 +127,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onMessageContentChange(text: CharSequence?) {
|
private fun onMessageContentChange(text: CharSequence?) {
|
||||||
formContentValue = text.toString()
|
formContentValue = (text as Spanned).toHtml()
|
||||||
presenter.onMessageContentChange()
|
presenter.onMessageContentChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,8 +149,8 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
|||||||
return presenter.onUpNavigate()
|
return presenter.onUpNavigate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setReportingUnit(unit: ReportingUnit) {
|
override fun setMailbox(mailbox: String) {
|
||||||
binding.sendMessageFrom.text = unit.senderName
|
binding.sendMessageFrom.text = mailbox
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setRecipients(recipients: List<RecipientChipItem>) {
|
override fun setRecipients(recipients: List<RecipientChipItem>) {
|
||||||
@ -165,7 +182,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setContent(content: String) {
|
override fun setContent(content: String) {
|
||||||
binding.sendMessageMessageContent.setText(content)
|
binding.sendMessageMessageContent.setText(content.parseAsHtml())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showMessage(text: String) {
|
override fun showMessage(text: String) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package io.github.wulkanowy.ui.modules.message.send
|
package io.github.wulkanowy.ui.modules.message.send
|
||||||
|
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
|
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||||
|
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.logResourceStatus
|
import io.github.wulkanowy.data.logResourceStatus
|
||||||
@ -25,9 +27,8 @@ import javax.inject.Inject
|
|||||||
class SendMessagePresenter @Inject constructor(
|
class SendMessagePresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val semesterRepository: SemesterRepository,
|
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
private val reportingUnitRepository: ReportingUnitRepository,
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val recipientRepository: RecipientRepository,
|
private val recipientRepository: RecipientRepository,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
@ -52,20 +53,21 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
message?.let {
|
message?.let {
|
||||||
setSubject(
|
setSubject(
|
||||||
when (reply) {
|
when (reply) {
|
||||||
true -> "Re: "
|
true -> "RE: "
|
||||||
else -> "FW: "
|
else -> "FW: "
|
||||||
} + message.subject
|
} + message.subject
|
||||||
)
|
)
|
||||||
if (preferencesRepository.fillMessageContent || reply != true) {
|
if (preferencesRepository.fillMessageContent || reply != true) {
|
||||||
setContent(
|
setContent(buildString {
|
||||||
when (reply) {
|
if (reply == true) {
|
||||||
true -> "\n\n"
|
append("<br><br>")
|
||||||
else -> ""
|
}
|
||||||
} + when (message.sender.isNotEmpty()) {
|
|
||||||
true -> "Od: ${message.sender}\n"
|
append("Od: ${message.sender}<br>")
|
||||||
false -> "Do: ${message.recipient}\n"
|
append("Do: ${message.recipients}<br>")
|
||||||
} + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}"
|
append("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}<br><br>")
|
||||||
)
|
append(message.content)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,21 +113,24 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
private fun loadData(message: Message?, reply: Boolean?) {
|
private fun loadData(message: Message?, reply: Boolean?) {
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
val unit = reportingUnitRepository.getReportingUnit(student, semester.unitId)
|
|
||||||
|
|
||||||
Timber.i("Loading recipients started")
|
Timber.i("Loading recipients started")
|
||||||
val recipients = when {
|
val recipients = createChips(
|
||||||
unit != null -> recipientRepository.getRecipients(student, unit, 2)
|
recipients = recipientRepository.getRecipients(
|
||||||
else -> listOf()
|
student = student,
|
||||||
}.let { createChips(it) }
|
mailbox = mailbox,
|
||||||
|
type = MailboxType.EMPLOYEE,
|
||||||
|
)
|
||||||
|
)
|
||||||
Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size)
|
Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size)
|
||||||
|
|
||||||
Timber.i("Loading message recipients started")
|
Timber.i("Loading message recipients started")
|
||||||
val messageRecipients = when {
|
val messageRecipients = when {
|
||||||
message != null && reply == true -> recipientRepository.getMessageRecipients(
|
message != null && reply == true -> recipientRepository.getMessageSender(
|
||||||
student,
|
student = student,
|
||||||
message
|
message = message,
|
||||||
|
mailbox = mailbox,
|
||||||
)
|
)
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}.let { createChips(it) }
|
}.let { createChips(it) }
|
||||||
@ -134,7 +139,7 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
messageRecipients.size
|
messageRecipients.size
|
||||||
)
|
)
|
||||||
|
|
||||||
Triple(unit, recipients, messageRecipients)
|
Triple(mailbox, recipients, messageRecipients)
|
||||||
}
|
}
|
||||||
.logResourceStatus("load recipients")
|
.logResourceStatus("load recipients")
|
||||||
.onEach {
|
.onEach {
|
||||||
@ -143,19 +148,14 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
showProgress(true)
|
showProgress(true)
|
||||||
showContent(false)
|
showContent(false)
|
||||||
}
|
}
|
||||||
is Resource.Success -> it.data.let { (reportingUnit, recipientChips, selectedRecipientChips) ->
|
is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) ->
|
||||||
view?.run {
|
view?.run {
|
||||||
if (reportingUnit != null) {
|
setMailbox(getMailboxName(mailbox))
|
||||||
setReportingUnit(reportingUnit)
|
setRecipients(recipientChips)
|
||||||
setRecipients(recipientChips)
|
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
|
||||||
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
|
selectedRecipientChips
|
||||||
selectedRecipientChips
|
)
|
||||||
)
|
showContent(true)
|
||||||
showContent(true)
|
|
||||||
} else {
|
|
||||||
Timber.i("Loading recipients result: Can't find the reporting unit")
|
|
||||||
view?.showEmpty(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
@ -171,7 +171,14 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
|
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
messageRepository.sendMessage(student, subject, content, recipients)
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
|
messageRepository.sendMessage(
|
||||||
|
student = student,
|
||||||
|
subject = subject,
|
||||||
|
content = content,
|
||||||
|
recipients = recipients,
|
||||||
|
mailboxId = mailbox.globalKey,
|
||||||
|
)
|
||||||
}.logResourceStatus("sending message").onEach {
|
}.logResourceStatus("sending message").onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Loading -> view?.run {
|
is Resource.Loading -> view?.run {
|
||||||
@ -201,31 +208,44 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createChips(recipients: List<Recipient>): List<RecipientChipItem> {
|
private fun createChips(recipients: List<Recipient>): List<RecipientChipItem> {
|
||||||
fun generateCorrectSummary(recipientRealName: String): String {
|
|
||||||
val substring = recipientRealName.substringBeforeLast("-")
|
|
||||||
return when {
|
|
||||||
substring == recipientRealName -> recipientRealName
|
|
||||||
substring.indexOf("(") != -1 -> {
|
|
||||||
recipientRealName.indexOf("(")
|
|
||||||
.let { recipientRealName.substring(if (it != -1) it else 0) }
|
|
||||||
}
|
|
||||||
substring.indexOf("[") != -1 -> {
|
|
||||||
recipientRealName.indexOf("[")
|
|
||||||
.let { recipientRealName.substring(if (it != -1) it else 0) }
|
|
||||||
}
|
|
||||||
else -> recipientRealName.substringAfter("-")
|
|
||||||
}.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipients.map {
|
return recipients.map {
|
||||||
RecipientChipItem(
|
RecipientChipItem(
|
||||||
title = it.name,
|
title = it.userName,
|
||||||
summary = generateCorrectSummary(it.realName),
|
summary = buildString {
|
||||||
|
getMailboxType(it.type)?.let(::append)
|
||||||
|
if (isNotBlank()) append(" ")
|
||||||
|
|
||||||
|
append("(${it.schoolShortName})")
|
||||||
|
},
|
||||||
recipient = it
|
recipient = it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getMailboxName(mailbox: Mailbox): String {
|
||||||
|
return buildString {
|
||||||
|
append(mailbox.userName)
|
||||||
|
append(" - ")
|
||||||
|
append(getMailboxType(mailbox.type))
|
||||||
|
|
||||||
|
if (mailbox.type == MailboxType.PARENT) {
|
||||||
|
append(" - ")
|
||||||
|
append(mailbox.studentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
append(" - ")
|
||||||
|
append("(${mailbox.schoolNameShort})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMailboxType(type: MailboxType): String? = when (type) {
|
||||||
|
MailboxType.STUDENT -> view?.mailboxStudent
|
||||||
|
MailboxType.PARENT -> view?.mailboxParent
|
||||||
|
MailboxType.GUARDIAN -> view?.mailboxGuardian
|
||||||
|
MailboxType.EMPLOYEE -> view?.mailboxEmployee
|
||||||
|
MailboxType.UNKNOWN -> null
|
||||||
|
}
|
||||||
|
|
||||||
fun onMessageContentChange() {
|
fun onMessageContentChange() {
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
messageUpdateChannel.send(Unit)
|
messageUpdateChannel.send(Unit)
|
||||||
@ -263,7 +283,7 @@ class SendMessagePresenter @Inject constructor(
|
|||||||
|
|
||||||
fun getRecipientsNames(): String {
|
fun getRecipientsNames(): String {
|
||||||
return messageRepository.draftMessage?.recipients.orEmpty()
|
return messageRepository.draftMessage?.recipients.orEmpty()
|
||||||
.joinToString { it.recipient.name }
|
.joinToString { it.recipient.userName }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearDraft() {
|
fun clearDraft() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.message.send
|
package io.github.wulkanowy.ui.modules.message.send
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface SendMessageView : BaseView {
|
interface SendMessageView : BaseView {
|
||||||
@ -18,9 +18,17 @@ interface SendMessageView : BaseView {
|
|||||||
|
|
||||||
val messageSuccess: String
|
val messageSuccess: String
|
||||||
|
|
||||||
|
val mailboxStudent: String
|
||||||
|
|
||||||
|
val mailboxParent: String
|
||||||
|
|
||||||
|
val mailboxGuardian: String
|
||||||
|
|
||||||
|
val mailboxEmployee: String
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun setReportingUnit(unit: ReportingUnit)
|
fun setMailbox(mailbox: String)
|
||||||
|
|
||||||
fun setRecipients(recipients: List<RecipientChipItem>)
|
fun setRecipients(recipients: List<RecipientChipItem>)
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
|
||||||
import io.github.wulkanowy.databinding.ItemMessageBinding
|
import io.github.wulkanowy.databinding.ItemMessageBinding
|
||||||
import io.github.wulkanowy.databinding.ItemMessageChipsBinding
|
import io.github.wulkanowy.databinding.ItemMessageChipsBinding
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
@ -88,12 +87,8 @@ class MessageTabAdapter @Inject constructor() :
|
|||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL
|
val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL
|
||||||
|
|
||||||
messageItemAuthor.run {
|
with(messageItemAuthor) {
|
||||||
text = if (message.folderId == MessageFolder.SENT.id) {
|
text = message.correspondents
|
||||||
message.recipient
|
|
||||||
} else {
|
|
||||||
message.sender
|
|
||||||
}
|
|
||||||
setTypeface(null, style)
|
setTypeface(null, style)
|
||||||
}
|
}
|
||||||
messageItemSubject.run {
|
messageItemSubject.run {
|
||||||
@ -145,7 +140,7 @@ class MessageTabAdapter @Inject constructor() :
|
|||||||
val newItem = new[newItemPosition]
|
val newItem = new[newItemPosition]
|
||||||
|
|
||||||
return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) {
|
return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) {
|
||||||
oldItem.message.id == newItem.message.id
|
oldItem.message.messageGlobalKey == newItem.message.messageGlobalKey
|
||||||
} else {
|
} else {
|
||||||
oldItem.viewType == newItem.viewType
|
oldItem.viewType == newItem.viewType
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package io.github.wulkanowy.ui.modules.message.tab
|
|||||||
import io.github.wulkanowy.data.*
|
import io.github.wulkanowy.data.*
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
import io.github.wulkanowy.data.enums.MessageFolder
|
||||||
|
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
@ -26,7 +26,7 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
private val semesterRepository: SemesterRepository,
|
private val mailboxRepository: MailboxRepository,
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper
|
||||||
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
|
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
@ -122,7 +122,8 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val student = studentRepository.getCurrentStudent(true)
|
val student = studentRepository.getCurrentStudent(true)
|
||||||
messageRepository.deleteMessages(student, messageList)
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
|
messageRepository.deleteMessages(student, mailbox, messageList)
|
||||||
}
|
}
|
||||||
.onFailure(errorHandler::dispatch)
|
.onFailure(errorHandler::dispatch)
|
||||||
.onSuccess { view?.showMessagesDeleted() }
|
.onSuccess { view?.showMessagesDeleted() }
|
||||||
@ -159,7 +160,7 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) {
|
fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) {
|
||||||
Timber.i("Select message ${messageItem.message.id} item (position: $position)")
|
Timber.i("Select message ${messageItem.message.messageGlobalKey} item (position: $position)")
|
||||||
|
|
||||||
if (!isActionMode) {
|
if (!isActionMode) {
|
||||||
view?.run {
|
view?.run {
|
||||||
@ -206,8 +207,8 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
|
|
||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val mailbox = mailboxRepository.getMailbox(student)
|
||||||
messageRepository.getMessages(student, semester, folder, forceRefresh)
|
messageRepository.getMessages(student, mailbox, folder, forceRefresh)
|
||||||
}
|
}
|
||||||
.logResourceStatus("load $folder message")
|
.logResourceStatus("load $folder message")
|
||||||
.onResourceData {
|
.onResourceData {
|
||||||
@ -333,7 +334,7 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
addAll(data.map { message ->
|
addAll(data.map { message ->
|
||||||
MessageTabDataItem.MessageItem(
|
MessageTabDataItem.MessageItem(
|
||||||
message = message,
|
message = message,
|
||||||
isSelected = messagesToDelete.any { it.id == message.id },
|
isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey },
|
||||||
isActionMode = isActionMode
|
isActionMode = isActionMode
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -345,10 +346,9 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
private fun calculateMatchRatio(message: Message, query: String): Int {
|
private fun calculateMatchRatio(message: Message, query: String): Int {
|
||||||
val subjectRatio = FuzzySearch.tokenSortPartialRatio(query.lowercase(), message.subject)
|
val subjectRatio = FuzzySearch.tokenSortPartialRatio(query.lowercase(), message.subject)
|
||||||
|
|
||||||
val senderOrRecipientRatio = FuzzySearch.tokenSortPartialRatio(
|
val correspondentsRatio = FuzzySearch.tokenSortPartialRatio(
|
||||||
query.lowercase(),
|
query.lowercase(),
|
||||||
if (message.sender.isNotEmpty()) message.sender.lowercase()
|
message.correspondents
|
||||||
else message.recipient.lowercase()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val dateRatio = listOf(
|
val dateRatio = listOf(
|
||||||
@ -364,7 +364,7 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
|
|
||||||
|
|
||||||
return (subjectRatio.toDouble().pow(2)
|
return (subjectRatio.toDouble().pow(2)
|
||||||
+ senderOrRecipientRatio.toDouble().pow(2)
|
+ correspondentsRatio.toDouble().pow(2)
|
||||||
+ dateRatio.toDouble().pow(2) * 2
|
+ dateRatio.toDouble().pow(2) * 2
|
||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ class NotificationsCenterFragment :
|
|||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
notificationsCenterAdapter.onItemClickListener = { notification ->
|
notificationsCenterAdapter.onItemClickListener = { notification ->
|
||||||
(requireActivity() as MainActivity).pushView(notification.destination.fragment)
|
(requireActivity() as MainActivity).pushView(notification.destination.destinationFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(binding.notificationsCenterRecycler) {
|
with(binding.notificationsCenterRecycler) {
|
||||||
|
@ -15,6 +15,8 @@ import io.github.wulkanowy.ui.modules.Destination
|
|||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SuppressLint("CustomSplashScreen")
|
@SuppressLint("CustomSplashScreen")
|
||||||
@ -29,13 +31,13 @@ class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val EXTRA_START_DESTINATION = "start_destination"
|
private const val EXTRA_START_DESTINATION = "start_destination_json"
|
||||||
|
|
||||||
private const val EXTRA_EXTERNAL_URL = "external_url"
|
private const val EXTRA_EXTERNAL_URL = "external_url"
|
||||||
|
|
||||||
fun getStartIntent(context: Context, destination: Destination? = null) =
|
fun getStartIntent(context: Context, destination: Destination? = null) =
|
||||||
Intent(context, SplashActivity::class.java).apply {
|
Intent(context, SplashActivity::class.java).apply {
|
||||||
putExtra(EXTRA_START_DESTINATION, destination)
|
destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) }
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,12 +45,12 @@ class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
installSplashScreen().setKeepOnScreenCondition { true }
|
installSplashScreen().setKeepOnScreenCondition { true }
|
||||||
|
shortcutsHelper.initializeShortcuts()
|
||||||
|
|
||||||
val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL)
|
val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL)
|
||||||
val startDestination = intent?.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?
|
val startDestinationJson = intent?.getStringExtra(EXTRA_START_DESTINATION)
|
||||||
?: shortcutsHelper.getDestination(intent)
|
|
||||||
|
|
||||||
presenter.onAttachView(this, externalLink, startDestination)
|
presenter.onAttachView(this, externalLink, startDestinationJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openLoginView() {
|
override fun openLoginView() {
|
||||||
|
@ -5,16 +5,21 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
|||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.ui.modules.Destination
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SplashPresenter @Inject constructor(
|
class SplashPresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
|
private val json: Json
|
||||||
) : BasePresenter<SplashView>(errorHandler, studentRepository) {
|
) : BasePresenter<SplashView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
fun onAttachView(view: SplashView, externalUrl: String?, startDestination: Destination?) {
|
fun onAttachView(view: SplashView, externalUrl: String?, startDestinationJson: String?) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
|
|
||||||
|
val startDestination: Destination? = startDestinationJson?.let { json.decodeFromString(it) }
|
||||||
|
|
||||||
if (!externalUrl.isNullOrBlank()) {
|
if (!externalUrl.isNullOrBlank()) {
|
||||||
view.openExternalUrlAndFinish(externalUrl)
|
view.openExternalUrlAndFinish(externalUrl)
|
||||||
return
|
return
|
||||||
|
@ -87,15 +87,19 @@ class TimetablePresenter @Inject constructor(
|
|||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
Timber.i("Timetable view is reselected")
|
Timber.i("Timetable view is reselected")
|
||||||
view?.also { view ->
|
view?.let { view ->
|
||||||
if (view.currentStackSize == 1) {
|
if (view.currentStackSize == 1) {
|
||||||
baseDate.also {
|
baseDate = now().nextOrSameSchoolDay
|
||||||
if (currentDate != it) {
|
|
||||||
reloadView(it)
|
if (currentDate != baseDate) {
|
||||||
loadData()
|
reloadView(baseDate)
|
||||||
} else if (!view.isViewEmpty) view.resetView()
|
loadData()
|
||||||
|
} else if (!view.isViewEmpty) {
|
||||||
|
view.resetView()
|
||||||
}
|
}
|
||||||
} else view.popView()
|
} else {
|
||||||
|
view.popView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetablewidget
|
package io.github.wulkanowy.ui.modules.timetablewidget
|
||||||
|
|
||||||
import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
import android.appwidget.AppWidgetManager.*
|
||||||
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
|
|
||||||
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -17,6 +15,7 @@ import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding
|
|||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.base.WidgetConfigureAdapter
|
import io.github.wulkanowy.ui.base.WidgetConfigureAdapter
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
|
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_CONFIGURE
|
||||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER
|
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -92,6 +91,7 @@ class TimetableWidgetConfigureActivity :
|
|||||||
.apply {
|
.apply {
|
||||||
action = ACTION_APPWIDGET_UPDATE
|
action = ACTION_APPWIDGET_UPDATE
|
||||||
putExtra(EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
|
putExtra(EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
|
||||||
|
putExtra(EXTRA_FROM_CONFIGURE, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetablewidget
|
package io.github.wulkanowy.ui.modules.timetablewidget
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetManager.*
|
import android.appwidget.AppWidgetManager.*
|
||||||
@ -61,6 +60,8 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
|||||||
|
|
||||||
private const val BUTTON_RESET = "buttonReset"
|
private const val BUTTON_RESET = "buttonReset"
|
||||||
|
|
||||||
|
const val EXTRA_FROM_CONFIGURE = "extraFromConfigure"
|
||||||
|
|
||||||
const val EXTRA_FROM_PROVIDER = "extraFromProvider"
|
const val EXTRA_FROM_PROVIDER = "extraFromProvider"
|
||||||
|
|
||||||
fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId"
|
fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId"
|
||||||
@ -87,12 +88,22 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onUpdate(context: Context, intent: Intent) {
|
private suspend fun onUpdate(context: Context, intent: Intent) {
|
||||||
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) {
|
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) == null) {
|
||||||
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId ->
|
val isFromConfigure = intent.getBooleanExtra(EXTRA_FROM_CONFIGURE, false)
|
||||||
|
val appWidgetIds = intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS) ?: return
|
||||||
|
|
||||||
|
appWidgetIds.forEach { appWidgetId ->
|
||||||
val student =
|
val student =
|
||||||
getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
|
getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
|
||||||
|
val savedDataEpochDay = sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)
|
||||||
|
|
||||||
updateWidget(context, appWidgetId, getWidgetDateToLoad(appWidgetId), student)
|
val dateToLoad = if (isFromConfigure && savedDataEpochDay != 0L) {
|
||||||
|
LocalDate.ofEpochDay(savedDataEpochDay)
|
||||||
|
} else {
|
||||||
|
getWidgetDefaultDateToLoad(appWidgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWidget(context, appWidgetId, dateToLoad, student)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE)
|
val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE)
|
||||||
@ -104,10 +115,10 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
|||||||
val savedDate =
|
val savedDate =
|
||||||
LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0))
|
LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0))
|
||||||
val date = when (buttonType) {
|
val date = when (buttonType) {
|
||||||
BUTTON_RESET -> getWidgetDateToLoad(toggledWidgetId)
|
BUTTON_RESET -> getWidgetDefaultDateToLoad(toggledWidgetId)
|
||||||
BUTTON_NEXT -> savedDate.nextSchoolDay
|
BUTTON_NEXT -> savedDate.nextSchoolDay
|
||||||
BUTTON_PREV -> savedDate.previousSchoolDay
|
BUTTON_PREV -> savedDate.previousSchoolDay
|
||||||
else -> getWidgetDateToLoad(toggledWidgetId)
|
else -> getWidgetDefaultDateToLoad(toggledWidgetId)
|
||||||
}
|
}
|
||||||
if (!buttonType.isNullOrBlank()) {
|
if (!buttonType.isNullOrBlank()) {
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
@ -132,7 +143,6 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
private fun updateWidget(
|
private fun updateWidget(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetId: Int,
|
appWidgetId: Int,
|
||||||
@ -273,7 +283,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
|||||||
return avatarBitmap
|
return avatarBitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWidgetDateToLoad(appWidgetId: Int): LocalDate {
|
private fun getWidgetDefaultDateToLoad(appWidgetId: Int): LocalDate {
|
||||||
val lastLessonEndTimestamp =
|
val lastLessonEndTimestamp =
|
||||||
sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0)
|
sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0)
|
||||||
val lastLessonEndDateTime =
|
val lastLessonEndDateTime =
|
||||||
|
@ -13,6 +13,7 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable
|
|||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int {
|
fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int {
|
||||||
val array = obtainStyledAttributes(null, intArrayOf(colorAttr))
|
val array = obtainStyledAttributes(null, intArrayOf(colorAttr))
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Wersja 1.6.0
|
Wersja 1.7.1
|
||||||
|
|
||||||
- dodaliśmy możliwość usuwania wielu wiadomości jednocześnie
|
- naprawiliśmy logowanie do aplikacji
|
||||||
- dodaliśmy opcję szybkiego dodawania sprawdzianów do kalendarza
|
- dodaliśmy wsparcie nowego modułu Wiadomości Plus
|
||||||
- dodaliśmy średnią ucznia w wykresach ocen klasy
|
- dodaliśmy nową możliwość wsparcia naszego projektu przez opcjonalne reklamy
|
||||||
- naprawiliśmy rzadki błąd dotyczący problemów z automatycznym odświeżaniem ekranu startowego
|
- dodaliśmy sortowanie po średniej
|
||||||
- naprawiliśmy błąd z liczeniem średniej w drugim semestrze
|
- naprawiliśmy też kilka usterek wpływających na komfort używania aplikacji
|
||||||
|
|
||||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
app:layout_constraintBottom_toTopOf="@id/sendMessageScroll"
|
app:layout_constraintBottom_toTopOf="@id/sendMessageScroll"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
tools:targetApi="lollipop" />
|
|
||||||
|
|
||||||
<io.github.wulkanowy.materialchipsinput.ConsumedNestedScrollView
|
<io.github.wulkanowy.materialchipsinput.ConsumedNestedScrollView
|
||||||
android:id="@+id/sendMessageScroll"
|
android:id="@+id/sendMessageScroll"
|
||||||
|
79
app/src/main/res/layout/dialog_ads_consent.xml
Normal file
79
app/src/main/res/layout/dialog_ads_consent.xml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/ads_consent_privacy"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:text="@string/pref_ads_privacy_policy"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/ads_consent_over"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="17dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/pref_ads_over_18_years_old"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ads_consent_privacy" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/ads_consent_personalised"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:text="@string/pref_ads_option_personalized"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ads_consent_over" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/ads_consent_non_personalised"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:text="@string/pref_ads_option_non_personalized"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/ads_consent_cancel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ads_consent_personalised"
|
||||||
|
app:layout_constraintVertical_bias="0" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/ads_consent_cancel"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -146,7 +146,7 @@
|
|||||||
android:id="@+id/examDialogDeadlineDateValue"
|
android:id="@+id/examDialogDeadlineDateValue"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="0dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:paddingStart="0dp"
|
android:paddingStart="0dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
@ -162,7 +162,7 @@
|
|||||||
android:id="@+id/examDialogEntryDateTitle"
|
android:id="@+id/examDialogEntryDateTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="0dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:text="@string/exam_entry_date"
|
android:text="@string/exam_entry_date"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user