Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
9a8fb593c0 | |||
f4c6e0ad1b | |||
b30b7c3318 | |||
897eac050a | |||
83974b6550 | |||
7efd106658 | |||
9cedab979c | |||
510e2d5b88 | |||
63d6a0b325 | |||
da0943b319 | |||
67875b1a9a | |||
aa3d7e37fc | |||
ede5914d70 | |||
09c968f273 | |||
345b580601 | |||
61240777cf | |||
7588345b6d | |||
2fa26c37a9 | |||
c34c63c128 | |||
a26dadb224 | |||
277ffd22be | |||
f1479d489b | |||
fba4e85311 | |||
4a5991ade4 | |||
a735c378f1 | |||
217ebfc549 | |||
df5155f1c7 | |||
b93c0222a2 | |||
083ca34f1b | |||
5d5dfd4eb4 | |||
429fdfa4a0 | |||
302d723cfb | |||
8f50ee82b3 | |||
9dc1220496 | |||
ae39bd94e5 | |||
85ce23845f | |||
890d60811b | |||
49e68f5c8b | |||
1df4679db8 | |||
e03aae2d56 | |||
9c60ce688b | |||
fdce2cf477 | |||
650cbd5a10 | |||
b160367744 | |||
6c115fb915 | |||
7a408899df | |||
4bce35f810 | |||
2d83218f61 | |||
d3e276d6fc | |||
51a1097bb4 | |||
db4f172fb8 | |||
4d49e956b8 | |||
b8296ac02f | |||
d6385e8cdd | |||
885319a885 | |||
fded5007c1 | |||
66ff14f719 | |||
1257dc63d3 | |||
50b6d380b6 | |||
62b7d42a73 | |||
21fe209246 | |||
02cd4e4e06 | |||
86fe2b61cb | |||
4113bd9b53 | |||
d924902dac | |||
b269360ecb | |||
ffd5addadb | |||
c5e2b18695 | |||
515a3973b7 | |||
7bee10d5ce | |||
22a4f509dc | |||
3925a6261b | |||
49b383fbe5 | |||
4a484dc2ce | |||
a14c4b489b | |||
e91cd18804 | |||
4c24363599 | |||
e20c232f8f | |||
1f11eea9b5 | |||
42f9a00e8c | |||
ad487e680c | |||
3f431022a5 | |||
cd037f0ce0 | |||
37f7f21a03 | |||
c653039590 | |||
95a90a7a79 | |||
4dc80595ac | |||
8114a2376e | |||
a523850216 | |||
354f51dd70 | |||
b271c12ebc | |||
8ca41b5ba3 | |||
edbe45332a | |||
1bbd249275 | |||
5148ff291b | |||
a1dc00af42 | |||
f1db993fee | |||
4f0519552e | |||
3625c5c518 | |||
afbfb9761f | |||
a5c636853a | |||
d5d45ed1ba | |||
d3f869c6c2 | |||
46c29c438e | |||
73a7255d3a | |||
c7af85e0e1 | |||
afc16e3d17 | |||
59f6f5c212 | |||
86f8763e69 | |||
157becb017 | |||
83ca9a7060 |
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -2,10 +2,12 @@ name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, develop ]
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- 'hotfix/**'
|
||||
tags: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master, develop ]
|
||||
|
||||
jobs:
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 Wulkanowy
|
||||
Copyright 2023 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
15
README.cs.md
15
README.cs.md
@ -1,18 +1,13 @@
|
||||
[English version of README](README.en.md)
|
||||
|
||||
[Deutsche Version von README](README.de.md)
|
||||
|
||||
[Polska wersja README](README.md)
|
||||
|
||||
[Slovenská verzia README](README.sk.md)
|
||||
Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
|
||||
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
|
||||
|
||||
@ -39,7 +34,7 @@ Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
|
||||
* podpora více účtů s možností přejmenování žáků
|
||||
* tmavý a černý (AMOLED) motiv
|
||||
* offline režim
|
||||
* žádné reklamy
|
||||
* volitelné reklamy na podporu projektu
|
||||
|
||||
## Stáhnout
|
||||
|
||||
@ -57,7 +52,7 @@ Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGal
|
||||
|
||||
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
|
||||
|
||||
## Postaveno s
|
||||
## Postaveno s pomocí
|
||||
|
||||
|
||||
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||
|
11
README.de.md
11
README.de.md
@ -1,14 +1,13 @@
|
||||
[Polska wersja README](README.md)
|
||||
|
||||
[English version of README](README.en.md)
|
||||
[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
|
||||
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
|
||||
|
||||
@ -22,7 +21,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
|
||||
* Prozentsatz der Anwesenheit
|
||||
* Prüfungen
|
||||
* Stundenplan
|
||||
* Unterricht abgeschlossen
|
||||
* abgeschlossene Unterrichtsstunden
|
||||
* Nachrichten
|
||||
* Hausaufgaben
|
||||
* Anmerkungen
|
||||
@ -35,7 +34,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
|
||||
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
|
||||
* dunkles und schwarzes (AMOLED) Thema
|
||||
* Offline-Modus
|
||||
* keine Werbung
|
||||
* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen
|
||||
|
||||
## Herunterladen
|
||||
|
||||
|
13
README.en.md
13
README.en.md
@ -1,18 +1,13 @@
|
||||
[Polska wersja README](README.md)
|
||||
|
||||
[Deutsche Version von README](README.de.md)
|
||||
|
||||
[Česká verze README](README.cs.md)
|
||||
|
||||
[Slovenská verzia README](README.sk.md)
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
|
||||
Unofficial android VULCAN UONET+ register client for both students and their parents
|
||||
|
||||
@ -39,7 +34,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
|
||||
* support for multiple accounts with the ability to rename students
|
||||
* dark and black (AMOLED) theme
|
||||
* offline mode
|
||||
* no ads
|
||||
* optional ads which allow to support the project
|
||||
|
||||
## Download
|
||||
|
||||
|
13
README.md
13
README.md
@ -1,18 +1,13 @@
|
||||
[English version of README](README.en.md)
|
||||
|
||||
[Deutsche Version von README](README.de.md)
|
||||
|
||||
[Česká verze README](README.cs.md)
|
||||
|
||||
[Slovenská verzia README](README.sk.md)
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
|
||||
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
|
||||
@ -39,7 +34,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
|
||||
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
|
||||
* ciemny i czarny (AMOLED) motyw
|
||||
* tryb offline
|
||||
* brak reklam
|
||||
* opcjonalne reklamy umożliwiające wsparcie projektu
|
||||
|
||||
## Pobierz
|
||||
|
||||
|
15
README.sk.md
15
README.sk.md
@ -1,18 +1,13 @@
|
||||
[English version of README](README.en.md)
|
||||
|
||||
[Deutsche Version von README](README.de.md)
|
||||
|
||||
[Polska wersja README](README.md)
|
||||
|
||||
[Česká verze README](README.cs.md)
|
||||
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[](https://discord.gg/vccAQBr)
|
||||
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||
[](https://translate.wulkanowy.net.pl)
|
||||
|
||||
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
|
||||
|
||||
@ -39,7 +34,7 @@ Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
|
||||
* podpora viacerých účtov s možnosťou premenovania žiakov
|
||||
* tmavý a čierny (AMOLED) motív
|
||||
* offline režim
|
||||
* žiadne reklamy
|
||||
* voliteľné reklamy na podporu projektu
|
||||
|
||||
## Stiahnuť
|
||||
|
||||
@ -57,7 +52,7 @@ Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGa
|
||||
|
||||
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
|
||||
|
||||
## Postavené s
|
||||
## Postavené s pomocou
|
||||
|
||||
|
||||
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||
|
@ -16,15 +16,15 @@ apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
namespace 'io.github.wulkanowy'
|
||||
compileSdkVersion 32
|
||||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 32
|
||||
versionCode 113
|
||||
versionName "1.7.4"
|
||||
targetSdkVersion 33
|
||||
versionCode 119
|
||||
versionName "1.9.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -160,9 +160,9 @@ kapt {
|
||||
play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
// releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
|
||||
// userFraction = 0.05d
|
||||
updatePriority = 5
|
||||
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.10d
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
@ -181,24 +181,24 @@ ext {
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.4.3"
|
||||
chucker = "3.5.2"
|
||||
mockk = "1.12.7"
|
||||
mockk = "1.13.3"
|
||||
coroutines = "1.6.4"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.7.4"
|
||||
implementation "io.github.wulkanowy:sdk:1.9.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.8.0"
|
||||
implementation "androidx.core:core-ktx:1.9.0"
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation "androidx.activity:activity-ktx:1.5.1"
|
||||
implementation "androidx.appcompat:appcompat:1.5.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
implementation "androidx.annotation:annotation:1.4.0"
|
||||
implementation "androidx.activity:activity-ktx:1.6.1"
|
||||
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.5"
|
||||
implementation "androidx.annotation:annotation:1.5.0"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
@ -206,10 +206,10 @@ dependencies {
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
implementation "com.google.android.material:material:1.6.1"
|
||||
implementation "com.google.android.material:material:1.7.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
||||
implementation 'com.github.lopspower:CircularImageView:4.3.0'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
@ -236,21 +236,22 @@ dependencies {
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||
implementation "io.coil-kt:coil:2.2.0"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
implementation "io.coil-kt:coil:2.2.2"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
implementation 'com.fredporciuncula:flow-preferences:1.8.0'
|
||||
implementation 'org.apache.commons:commons-text:1.10.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:30.3.2')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:31.1.1')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
playImplementation 'com.google.android.play:core:1.10.3'
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
playImplementation 'com.google.android.gms:play-services-ads:21.1.0'
|
||||
playImplementation 'com.google.android.gms:play-services-ads:21.4.0'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
@ -263,17 +264,17 @@ dependencies {
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.8.2'
|
||||
testImplementation "androidx.test:runner:1.4.0"
|
||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||
testImplementation "androidx.test:core:1.4.0"
|
||||
testImplementation 'org.robolectric:robolectric:4.9.1'
|
||||
testImplementation "androidx.test:runner:1.5.1"
|
||||
testImplementation "androidx.test.ext:junit:1.1.4"
|
||||
testImplementation "androidx.test:core:1.5.0"
|
||||
testImplementation "androidx.room:room-testing:$room"
|
||||
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
|
||||
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.4.0"
|
||||
androidTestImplementation "androidx.test:runner:1.4.0"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
||||
androidTestImplementation "androidx.test:core:1.5.0"
|
||||
androidTestImplementation "androidx.test:runner:1.5.1"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.4"
|
||||
androidTestImplementation "io.mockk:mockk-android:$mockk"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
}
|
||||
|
2421
app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json
Normal file
2421
app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json
Normal file
File diff suppressed because it is too large
Load Diff
2439
app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json
Normal file
2439
app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json
Normal file
File diff suppressed because it is too large
Load Diff
2439
app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json
Normal file
2439
app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/colorPrimary" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
|
||||
</adaptive-icon>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground_dev_mono" />
|
||||
</adaptive-icon>
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/colorPrimary" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
|
||||
</adaptive-icon>
|
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@ -8,15 +8,7 @@ import javax.inject.Singleton
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class AnalyticsHelper @Inject constructor() {
|
||||
|
||||
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
fun setCurrentScreen(activity: Activity, name: String?) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
fun popCurrentScreen(name: String?) {
|
||||
// do nothing
|
||||
}
|
||||
fun logEvent(name: String, vararg params: Pair<String, Any?>) = Unit
|
||||
fun setCurrentScreen(activity: Activity, name: String?) = Unit
|
||||
fun popCurrentScreen(name: String?) = Unit
|
||||
}
|
||||
|
@ -3,26 +3,38 @@ package io.github.wulkanowy.utils
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.huawei.agconnect.crash.AGConnectCrash
|
||||
import com.huawei.hms.analytics.HiAnalytics
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AnalyticsHelper @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
@ApplicationContext private val context: Context,
|
||||
preferencesRepository: PreferencesRepository,
|
||||
appInfo: AppInfo,
|
||||
) {
|
||||
|
||||
private val analytics by lazy { HiAnalytics.getInstance(context) }
|
||||
|
||||
private val connectCrash by lazy { AGConnectCrash.getInstance() }
|
||||
|
||||
init {
|
||||
if (!appInfo.isDebug) {
|
||||
connectCrash.setUserId(preferencesRepository.installationId)
|
||||
}
|
||||
}
|
||||
|
||||
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
|
||||
Bundle().apply {
|
||||
params.forEach {
|
||||
if (it.second == null) return@forEach
|
||||
when (it.second) {
|
||||
is String, is String? -> putString(it.first, it.second as String)
|
||||
is Int, is Int? -> putInt(it.first, it.second as Int)
|
||||
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
|
||||
params.forEach { (key, value) ->
|
||||
if (value == null) return@forEach
|
||||
when (value) {
|
||||
is String -> putString(key, value)
|
||||
is Int -> putInt(key, value)
|
||||
is Boolean -> putBoolean(key, value)
|
||||
}
|
||||
}
|
||||
analytics.onEvent(name, this)
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
|
||||
import android.util.Log
|
||||
import com.huawei.agconnect.crash.AGConnectCrash
|
||||
import fr.bipi.tressence.base.FormatterPriorityTree
|
||||
import fr.bipi.tressence.common.StackTraceRecorder
|
||||
|
||||
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
|
||||
|
||||
@ -22,16 +23,10 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
if (skipLog(priority, tag, message, t)) return
|
||||
|
||||
// Disabled due to a bug in the Huawei library
|
||||
|
||||
/*connectCrash.setCustomKey("priority", priority)
|
||||
connectCrash.setCustomKey("tag", tag.orEmpty())
|
||||
connectCrash.setCustomKey("message", message)
|
||||
|
||||
if (t != null) {
|
||||
connectCrash.recordException(t)
|
||||
} else {
|
||||
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,8 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
@ -36,13 +37,14 @@
|
||||
<application
|
||||
android:name=".WulkanowyApp"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
|
@ -49,8 +49,8 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
|
||||
|
||||
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
|
||||
val description = when (it) {
|
||||
is Resource.Loading -> "started"
|
||||
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
|
||||
is Resource.Loading -> "started"
|
||||
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
|
||||
is Resource.Error -> "exception occurred: ${it.error}"
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import javax.inject.Singleton
|
||||
AutoMigration(from = 44, to = 45),
|
||||
AutoMigration(from = 46, to = 47),
|
||||
AutoMigration(from = 47, to = 48),
|
||||
AutoMigration(from = 51, to = 52),
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -55,7 +56,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 51
|
||||
const val VERSION_SCHEMA = 54
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -105,6 +106,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration49(),
|
||||
Migration50(),
|
||||
Migration51(),
|
||||
Migration53(),
|
||||
Migration54(),
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
|
@ -3,12 +3,16 @@ package io.github.wulkanowy.data.db.dao
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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 email = :email")
|
||||
suspend fun loadAll(email: String): List<Mailbox>
|
||||
|
||||
@Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId")
|
||||
fun loadAll(email: String, symbol: String, schoolId: String): Flow<List<Mailbox>>
|
||||
}
|
||||
|
@ -16,4 +16,7 @@ interface MessagesDao : BaseDao<Message> {
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
|
||||
fun loadAll(folder: Int, email: String): Flow<List<Message>>
|
||||
}
|
||||
|
@ -13,4 +13,7 @@ interface TimetableDao : BaseDao<Timetable> {
|
||||
|
||||
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
|
||||
|
||||
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
|
||||
}
|
||||
|
@ -1,20 +1,27 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "Mailboxes")
|
||||
data class Mailbox(
|
||||
|
||||
@PrimaryKey
|
||||
val globalKey: String,
|
||||
|
||||
val email: String,
|
||||
val symbol: String,
|
||||
val schoolId: String,
|
||||
|
||||
val fullName: String,
|
||||
val userName: String,
|
||||
val userLoginId: Int,
|
||||
val studentName: String,
|
||||
val schoolNameShort: String,
|
||||
val type: MailboxType,
|
||||
)
|
||||
) : java.io.Serializable, Parcelable
|
||||
|
||||
enum class MailboxType {
|
||||
STUDENT,
|
||||
|
@ -9,6 +9,9 @@ import java.time.Instant
|
||||
@Entity(tableName = "Messages")
|
||||
data class Message(
|
||||
|
||||
@ColumnInfo(name = "email")
|
||||
val email: String,
|
||||
|
||||
@ColumnInfo(name = "message_global_key")
|
||||
val messageGlobalKey: String,
|
||||
|
||||
@ -29,6 +32,12 @@ data class Message(
|
||||
|
||||
var unread: Boolean,
|
||||
|
||||
@ColumnInfo(name = "read_by")
|
||||
val readBy: Int?,
|
||||
|
||||
@ColumnInfo(name = "unread_by")
|
||||
val unreadBy: Int?,
|
||||
|
||||
@ColumnInfo(name = "has_attachments")
|
||||
val hasAttachments: Boolean
|
||||
) : Serializable {
|
||||
|
@ -0,0 +1,57 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration53 : Migration(52, 53) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
createMailboxTable(database)
|
||||
recreateMessagesTable(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,
|
||||
`email` TEXT NOT NULL,
|
||||
`symbol` TEXT NOT NULL,
|
||||
`schoolId` TEXT NOT NULL,
|
||||
`fullName` TEXT NOT NULL,
|
||||
`userName` TEXT 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` (
|
||||
`email` TEXT NOT NULL,
|
||||
`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,
|
||||
`read_by` INTEGER,
|
||||
`unread_by` INTEGER,
|
||||
`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()
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration54 : Migration(53, 54) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
migrateResman(database)
|
||||
removeTomaszowMazowieckiStudents(database)
|
||||
}
|
||||
|
||||
private fun migrateResman(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("""
|
||||
UPDATE Students SET
|
||||
scrapper_base_url = 'https://vulcan.net.pl',
|
||||
login_type = 'ADFSLightScoped',
|
||||
symbol = 'rzeszowprojekt'
|
||||
WHERE scrapper_base_url = 'https://resman.pl'
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun removeTomaszowMazowieckiStudents(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'")
|
||||
}
|
||||
}
|
@ -3,17 +3,22 @@ package io.github.wulkanowy.data.mappers
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
|
||||
import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary
|
||||
|
||||
fun List<SdkAttendance>.mapToEntities(semester: Semester) = map {
|
||||
fun List<SdkAttendance>.mapToEntities(semester: Semester, lessons: List<Timetable>) = map {
|
||||
Attendance(
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
date = it.date,
|
||||
timeId = it.timeId,
|
||||
number = it.number,
|
||||
subject = it.subject,
|
||||
subject = it.subject.ifBlank {
|
||||
lessons.find { lesson ->
|
||||
lesson.date == it.date && lesson.number == it.number
|
||||
}?.subject.orEmpty()
|
||||
},
|
||||
name = it.name,
|
||||
presence = it.presence,
|
||||
absence = it.absence,
|
||||
|
@ -10,9 +10,11 @@ fun List<SdkMailbox>.mapToEntities(student: Student) = map {
|
||||
globalKey = it.globalKey,
|
||||
fullName = it.fullName,
|
||||
userName = it.userName,
|
||||
userLoginId = student.userLoginId,
|
||||
studentName = it.studentName,
|
||||
schoolNameShort = it.schoolNameShort,
|
||||
type = MailboxType.valueOf(it.type.name),
|
||||
email = student.email,
|
||||
symbol = student.symbol,
|
||||
schoolId = student.schoolSymbol,
|
||||
)
|
||||
}
|
||||
|
@ -2,21 +2,36 @@ package io.github.wulkanowy.data.mappers
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
import io.github.wulkanowy.sdk.pojo.MailboxType
|
||||
import timber.log.Timber
|
||||
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||
|
||||
fun List<SdkMessage>.mapToEntities(mailbox: Mailbox) = map {
|
||||
fun List<SdkMessage>.mapToEntities(
|
||||
student: Student,
|
||||
mailbox: Mailbox?,
|
||||
allMailboxes: List<Mailbox>
|
||||
): List<Message> = map {
|
||||
Message(
|
||||
messageGlobalKey = it.globalKey,
|
||||
mailboxKey = mailbox.globalKey,
|
||||
mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
|
||||
box.fullName == it.mailbox
|
||||
}?.globalKey.let { mailboxKey ->
|
||||
if (mailboxKey == null) {
|
||||
Timber.e("Can't find ${it.mailbox} in $allMailboxes")
|
||||
"unknown"
|
||||
} else mailboxKey
|
||||
},
|
||||
email = student.email,
|
||||
messageId = it.id,
|
||||
correspondents = it.correspondents,
|
||||
subject = it.subject.trim(),
|
||||
date = it.dateZoned.toInstant(),
|
||||
folderId = it.folderId,
|
||||
unread = it.unread,
|
||||
hasAttachments = it.hasAttachments
|
||||
unreadBy = it.unreadBy,
|
||||
readBy = it.readBy,
|
||||
hasAttachments = it.hasAttachments,
|
||||
).apply {
|
||||
content = it.content.orEmpty()
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package io.github.wulkanowy.data.mappers
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.*
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.mapper.mapSemesters
|
||||
import java.time.Instant
|
||||
import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent
|
||||
import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser
|
||||
|
||||
fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser(
|
||||
email = email,
|
||||
login = login,
|
||||
password = password,
|
||||
baseUrl = baseUrl,
|
||||
loginType = loginType,
|
||||
symbols = symbols.map { registerSymbol ->
|
||||
RegisterSymbol(
|
||||
symbol = registerSymbol.symbol,
|
||||
error = registerSymbol.error,
|
||||
userName = registerSymbol.userName,
|
||||
schools = registerSymbol.schools.map {
|
||||
RegisterUnit(
|
||||
userLoginId = it.userLoginId,
|
||||
schoolId = it.schoolId,
|
||||
schoolName = it.schoolName,
|
||||
schoolShortName = it.schoolShortName,
|
||||
parentIds = it.parentIds,
|
||||
studentIds = it.studentIds,
|
||||
employeeIds = it.employeeIds,
|
||||
error = it.error,
|
||||
students = it.subjects
|
||||
.filterIsInstance<SdkRegisterStudent>()
|
||||
.map { registerSubject ->
|
||||
RegisterStudent(
|
||||
studentId = registerSubject.studentId,
|
||||
studentName = registerSubject.studentName,
|
||||
studentSecondName = registerSubject.studentSecondName,
|
||||
studentSurname = registerSubject.studentSurname,
|
||||
className = registerSubject.className,
|
||||
classId = registerSubject.classId,
|
||||
isParent = registerSubject.isParent,
|
||||
semesters = registerSubject.semesters
|
||||
.mapSemesters()
|
||||
.mapToEntities(registerSubject.studentId),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
fun RegisterStudent.mapToStudentWithSemesters(
|
||||
user: RegisterUser,
|
||||
symbol: RegisterSymbol,
|
||||
unit: RegisterUnit,
|
||||
colors: List<Long>,
|
||||
): StudentWithSemesters = StudentWithSemesters(
|
||||
semesters = semesters,
|
||||
student = Student(
|
||||
email = user.login, // for compatibility
|
||||
userName = symbol.userName,
|
||||
userLoginId = unit.userLoginId,
|
||||
isParent = isParent,
|
||||
className = className,
|
||||
classId = classId,
|
||||
studentId = studentId,
|
||||
symbol = symbol.symbol,
|
||||
loginType = user.loginType.name,
|
||||
schoolName = unit.schoolName,
|
||||
schoolShortName = unit.schoolShortName,
|
||||
schoolSymbol = unit.schoolId,
|
||||
studentName = "$studentName $studentSurname",
|
||||
loginMode = Sdk.Mode.SCRAPPER.name,
|
||||
scrapperBaseUrl = user.baseUrl,
|
||||
mobileBaseUrl = "",
|
||||
certificateKey = "",
|
||||
privateKey = "",
|
||||
password = user.password,
|
||||
isCurrent = false,
|
||||
registrationDate = Instant.now(),
|
||||
).apply {
|
||||
avatarColor = colors.random()
|
||||
},
|
||||
)
|
@ -0,0 +1,43 @@
|
||||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.sdk.scrapper.Scrapper
|
||||
|
||||
data class RegisterUser(
|
||||
val email: String,
|
||||
val password: String,
|
||||
val login: String, // may be the same as email
|
||||
val baseUrl: String,
|
||||
val loginType: Scrapper.LoginType,
|
||||
val symbols: List<RegisterSymbol>,
|
||||
) : java.io.Serializable
|
||||
|
||||
data class RegisterSymbol(
|
||||
val symbol: String,
|
||||
val error: Throwable?,
|
||||
val userName: String,
|
||||
val schools: List<RegisterUnit>,
|
||||
) : java.io.Serializable
|
||||
|
||||
data class RegisterUnit(
|
||||
val userLoginId: Int,
|
||||
val schoolId: String,
|
||||
val schoolName: String,
|
||||
val schoolShortName: String,
|
||||
val parentIds: List<Int>,
|
||||
val studentIds: List<Int>,
|
||||
val employeeIds: List<Int>,
|
||||
val error: Throwable?,
|
||||
val students: List<RegisterStudent>,
|
||||
) : java.io.Serializable
|
||||
|
||||
data class RegisterStudent(
|
||||
val studentId: Int,
|
||||
val studentName: String,
|
||||
val studentSecondName: String,
|
||||
val studentSurname: String,
|
||||
val className: String,
|
||||
val classId: Int,
|
||||
val isParent: Boolean,
|
||||
val semesters: List<Semester>,
|
||||
) : java.io.Serializable
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -9,8 +10,10 @@ import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Absent
|
||||
import io.github.wulkanowy.utils.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@ -20,6 +23,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class AttendanceRepository @Inject constructor(
|
||||
private val attendanceDb: AttendanceDao,
|
||||
private val timetableDb: TimetableDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
@ -48,10 +52,15 @@ class AttendanceRepository @Inject constructor(
|
||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
},
|
||||
fetch = {
|
||||
val lessons = withContext(Dispatchers.IO) {
|
||||
timetableDb.load(
|
||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||
)
|
||||
}
|
||||
sdk.init(student)
|
||||
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
|
||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||
.mapToEntities(semester)
|
||||
.mapToEntities(semester, lessons)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
|
@ -1,83 +0,0 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MailboxRepository @Inject constructor(
|
||||
private val mailboxDao: MailboxDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
private val cacheKey = "mailboxes"
|
||||
|
||||
suspend fun refreshMailboxes(student: Student) {
|
||||
val new = sdk.init(student).getMailboxes().mapToEntities(student)
|
||||
val old = mailboxDao.loadAll(student.userLoginId)
|
||||
|
||||
mailboxDao.deleteAll(old uniqueSubtract new)
|
||||
mailboxDao.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
||||
suspend fun getMailbox(student: Student): Mailbox {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
val mailboxes = mailboxDao.loadAll(student.userLoginId)
|
||||
val mailbox = mailboxes.filterByStudent(student)
|
||||
|
||||
return if (isExpired || mailbox == null) {
|
||||
refreshMailboxes(student)
|
||||
val newMailbox = mailboxDao.loadAll(student.userLoginId).filterByStudent(student)
|
||||
|
||||
requireNotNull(newMailbox) {
|
||||
"Mailbox for ${student.userName} - ${student.studentName} not found! Saved mailboxes: $mailboxes"
|
||||
}
|
||||
|
||||
newMailbox
|
||||
} else mailbox
|
||||
}
|
||||
|
||||
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
|
||||
val normalizedStudentName = student.studentName.normalizeStudentName()
|
||||
|
||||
return find {
|
||||
it.studentName.normalizeStudentName() == normalizedStudentName
|
||||
} ?: singleOrNull {
|
||||
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
|
||||
} ?: singleOrNull {
|
||||
it.studentName.getUnauthorizedVersion() == normalizedStudentName
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.normalizeStudentName(): String {
|
||||
return trim().split(" ").joinToString(" ") { part ->
|
||||
part.lowercase().replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.getFirstAndLastPart(): String {
|
||||
val parts = normalizeStudentName().split(" ")
|
||||
|
||||
val endParts = parts.filterIndexed { i, _ ->
|
||||
i == 0 || parts.size == i - 1
|
||||
}
|
||||
return endParts.joinToString(" ")
|
||||
}
|
||||
|
||||
private fun String.getUnauthorizedVersion(): String {
|
||||
return normalizeStudentName().split(" ")
|
||||
.joinToString(" ") {
|
||||
it.first() + "*".repeat(it.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@ package io.github.wulkanowy.data.repositories
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
@ -13,8 +14,8 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Folder
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
@ -40,16 +41,18 @@ class MessageRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sharedPrefProvider: SharedPrefProvider,
|
||||
private val json: Json,
|
||||
private val mailboxDao: MailboxDao,
|
||||
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "message"
|
||||
private val messagesCacheKey = "message"
|
||||
private val mailboxCacheKey = "mailboxes"
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(
|
||||
student: Student,
|
||||
mailbox: Mailbox,
|
||||
mailbox: Mailbox?,
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
@ -58,16 +61,20 @@ class MessageRepository @Inject constructor(
|
||||
isResultEmpty = { it.isEmpty() },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student, folder)
|
||||
key = getRefreshKey(messagesCacheKey, mailbox, folder)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
|
||||
query = {
|
||||
if (mailbox == null) {
|
||||
messagesDb.loadAll(folder.id, student.email)
|
||||
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).getMessages(
|
||||
folder = Folder.valueOf(folder.name),
|
||||
mailboxKey = mailbox.globalKey,
|
||||
).mapToEntities(mailbox)
|
||||
mailboxKey = mailbox?.globalKey,
|
||||
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
messagesDb.deleteAll(old uniqueSubtract new)
|
||||
@ -75,7 +82,9 @@ class MessageRepository @Inject constructor(
|
||||
it.isNotified = !notify
|
||||
})
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
|
||||
refreshHelper.updateLastRefreshTimestamp(
|
||||
getRefreshKey(messagesCacheKey, mailbox, folder)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@ -90,7 +99,9 @@ class MessageRepository @Inject constructor(
|
||||
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
||||
it.message.unread || it.message.content.isBlank()
|
||||
},
|
||||
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||
query = {
|
||||
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead)
|
||||
},
|
||||
@ -113,8 +124,10 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
fun getMessagesFromDatabase(mailbox: Mailbox): Flow<List<Message>> {
|
||||
return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
|
||||
fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow<List<Message>> {
|
||||
return if (mailbox == null) {
|
||||
messagesDb.loadAll(RECEIVED.id, student.email)
|
||||
} else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
|
||||
}
|
||||
|
||||
suspend fun updateMessages(messages: List<Message>) {
|
||||
@ -136,7 +149,7 @@ class MessageRepository @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List<Message>) {
|
||||
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
val firstMessage = messages.first()
|
||||
sdk.init(student).deleteMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
@ -165,10 +178,44 @@ class MessageRepository @Inject constructor(
|
||||
).first()
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
|
||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
|
||||
deleteMessages(student, mailbox, listOf(message))
|
||||
}
|
||||
|
||||
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { it.isEmpty() },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(mailboxCacheKey, student),
|
||||
)
|
||||
it.isEmpty() || isExpired || forceRefresh
|
||||
},
|
||||
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
|
||||
fetch = {
|
||||
sdk.init(student).getMailboxes().mapToEntities(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
mailboxDao.deleteAll(old uniqueSubtract new)
|
||||
mailboxDao.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
suspend fun getMailboxByStudent(student: Student): Mailbox? {
|
||||
val mailbox = getMailboxByStudentUseCase(student)
|
||||
|
||||
return if (mailbox == null) {
|
||||
getMailboxes(student, forceRefresh = true)
|
||||
.onResourceError { throw it }
|
||||
.onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") }
|
||||
.waitForResult()
|
||||
|
||||
getMailboxByStudentUseCase(student)
|
||||
} else mailbox
|
||||
}
|
||||
|
||||
var draftMessage: MessageDraft?
|
||||
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft))
|
||||
?.let { json.decodeFromString(it) }
|
||||
|
@ -10,17 +10,16 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.enums.*
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Singleton
|
||||
class PreferencesRepository @Inject constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
@ -316,6 +315,16 @@ class PreferencesRepository @Inject constructor(
|
||||
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
|
||||
}
|
||||
|
||||
var installationId: String
|
||||
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
||||
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
|
||||
|
||||
init {
|
||||
if (installationId.isEmpty()) {
|
||||
installationId = UUID.randomUUID().toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
|
||||
|
||||
private fun getLong(id: String, default: Int) =
|
||||
@ -331,23 +340,14 @@ class PreferencesRepository @Inject constructor(
|
||||
private fun getBoolean(id: String, default: Int) =
|
||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
|
||||
private fun getBoolean(id: Int, default: Boolean) =
|
||||
sharedPref.getBoolean(context.getString(id), default)
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val PREF_KEY_INSTALLATION_ID = "installation_id"
|
||||
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
|
||||
|
||||
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"
|
||||
|
||||
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,11 @@ class RecipientRepository @Inject constructor(
|
||||
|
||||
suspend fun getRecipients(
|
||||
student: Student,
|
||||
mailbox: Mailbox,
|
||||
type: MailboxType
|
||||
mailbox: Mailbox?,
|
||||
type: MailboxType,
|
||||
): List<Recipient> {
|
||||
mailbox ?: return emptyList()
|
||||
|
||||
val cached = recipientDb.loadAll(type, mailbox.globalKey)
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
@ -47,11 +49,15 @@ class RecipientRepository @Inject constructor(
|
||||
|
||||
suspend fun getMessageSender(
|
||||
student: Student,
|
||||
mailbox: Mailbox,
|
||||
message: Message
|
||||
): List<Recipient> = sdk.init(student)
|
||||
.getMessageReplayDetails(message.messageGlobalKey)
|
||||
.sender
|
||||
.let(::listOf)
|
||||
.mapToEntities(mailbox.globalKey)
|
||||
mailbox: Mailbox?,
|
||||
message: Message,
|
||||
): List<Recipient> {
|
||||
mailbox ?: return emptyList()
|
||||
|
||||
return sdk.init(student)
|
||||
.getMessageReplayDetails(message.messageGlobalKey)
|
||||
.sender
|
||||
.let(::listOf)
|
||||
.mapToEntities(mailbox.globalKey)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToPojo
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
@ -52,6 +54,14 @@ class StudentRepository @Inject constructor(
|
||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getUserSubjectsFromScrapper(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToPojo(password)
|
||||
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
password: String,
|
||||
|
@ -0,0 +1,65 @@
|
||||
package io.github.wulkanowy.domain.messages
|
||||
|
||||
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 javax.inject.Inject
|
||||
|
||||
class GetMailboxByStudentUseCase @Inject constructor(
|
||||
private val mailboxDao: MailboxDao,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(student: Student): Mailbox? {
|
||||
return mailboxDao.loadAll(student.email)
|
||||
.filterByStudent(student)
|
||||
}
|
||||
|
||||
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
|
||||
val normalizedStudentName = student.studentName.normalizeStudentName()
|
||||
|
||||
return singleOrNull {
|
||||
it.studentName.normalizeStudentName() == normalizedStudentName
|
||||
} ?: singleOrNull {
|
||||
it.studentName.normalizeStudentName() == normalizedStudentName
|
||||
&& it.schoolNameShort == student.schoolShortName
|
||||
} ?: singleOrNull {
|
||||
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
|
||||
} ?: singleOrNull {
|
||||
it.studentName.getReversedName() == normalizedStudentName
|
||||
} ?: singleOrNull {
|
||||
it.studentName.getUnauthorizedVersion() == normalizedStudentName
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.normalizeStudentName(): String {
|
||||
return trim().split(" ")
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(" ") { part ->
|
||||
part.lowercase().replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.getFirstAndLastPart(): String {
|
||||
val parts = normalizeStudentName().split(" ")
|
||||
|
||||
val endParts = parts.filterIndexed { i, _ ->
|
||||
i == 0 || parts.size - 1 == i
|
||||
}
|
||||
return endParts.joinToString(" ")
|
||||
}
|
||||
|
||||
private fun String.getReversedName(): String {
|
||||
val parts = normalizeStudentName().split(" ")
|
||||
|
||||
return parts
|
||||
.asReversed()
|
||||
.joinToString(" ")
|
||||
}
|
||||
|
||||
private fun String.getUnauthorizedVersion(): String {
|
||||
return normalizeStudentName().split(" ")
|
||||
.joinToString(" ") {
|
||||
it.first() + "*".repeat(it.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||
import io.github.wulkanowy.utils.descriptionRes
|
||||
import io.github.wulkanowy.utils.getPlural
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
@ -22,8 +21,9 @@ class NewAttendanceNotification @Inject constructor(
|
||||
suspend fun notify(items: List<Attendance>, student: Student) {
|
||||
val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
|
||||
.map {
|
||||
val lesson = it.subject.ifBlank { "Lekcja ${it.number}" }
|
||||
val description = context.getString(it.descriptionRes)
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description"
|
||||
"${it.date.toFormattedString("dd.MM")} - $lesson: $description"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
|
||||
|
@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||
import io.github.wulkanowy.utils.getPlural
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
|
||||
@ -12,12 +11,11 @@ import javax.inject.Inject
|
||||
|
||||
class MessageWork @Inject constructor(
|
||||
private val messageRepository: MessageRepository,
|
||||
private val mailboxRepository: MailboxRepository,
|
||||
private val newMessageNotification: NewMessageNotification,
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
messageRepository.getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
@ -26,7 +24,7 @@ class MessageWork @Inject constructor(
|
||||
notify = notify
|
||||
).waitForResult()
|
||||
|
||||
messageRepository.getMessagesFromDatabase(mailbox).first()
|
||||
messageRepository.getMessagesFromDatabase(student, mailbox).first()
|
||||
.filter { !it.isNotified && it.unread }.let {
|
||||
if (it.isNotEmpty()) newMessageNotification.notify(it, student)
|
||||
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })
|
||||
|
@ -1,22 +1,23 @@
|
||||
package io.github.wulkanowy.services.sync.works
|
||||
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.MailboxRepository
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.RecipientRepository
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecipientWork @Inject constructor(
|
||||
private val mailboxRepository: MailboxRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val recipientRepository: RecipientRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
|
||||
mailboxRepository.refreshMailboxes(student)
|
||||
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
|
||||
recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE)
|
||||
val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult()
|
||||
mailboxes.dataOrNull?.forEach {
|
||||
recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@ -15,6 +14,7 @@ import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||
import io.github.wulkanowy.utils.*
|
||||
import javax.inject.Inject
|
||||
@ -25,6 +25,9 @@ class ErrorDialog : DialogFragment() {
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
private const val ARGUMENT_KEY = "error"
|
||||
|
||||
@ -34,9 +37,9 @@ class ErrorDialog : DialogFragment() {
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
|
||||
val error = requireArguments().serializable<Throwable>(ARGUMENT_KEY)
|
||||
|
||||
val binding = DialogErrorBinding.inflate(LayoutInflater.from(context))
|
||||
val binding = DialogErrorBinding.inflate(layoutInflater)
|
||||
binding.bindErrorDetails(error)
|
||||
|
||||
return getAlertDialog(binding, error).apply {
|
||||
@ -99,7 +102,8 @@ class ErrorDialog : DialogFragment() {
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
preferencesRepository.installationId,
|
||||
) + "\n" + content,
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser(
|
||||
|
@ -1,6 +1,9 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.GET_ACTIVITIES
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
@ -41,9 +44,8 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
)
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity) =
|
||||
activity.packageManager
|
||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean =
|
||||
getPackageInfo(activity)
|
||||
.activities
|
||||
.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme
|
||||
@ -52,4 +54,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
||||
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getPackageInfo(activity: AppCompatActivity): PackageInfo {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
activity.packageManager.getPackageInfo(
|
||||
activity.packageName,
|
||||
PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
|
||||
)
|
||||
} else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentAboutBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
|
||||
@ -30,6 +31,9 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
override val versionRes: Triple<String, String, Drawable?>?
|
||||
get() = context?.run {
|
||||
val buildTimestamp =
|
||||
@ -185,7 +189,8 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}"
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
preferencesRepository.installationId,
|
||||
),
|
||||
onActivityNotFound = {
|
||||
requireContext().openInternetBrowser(
|
||||
|
@ -34,6 +34,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
override val titleStringId = R.string.account_title
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -6,6 +6,7 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -37,12 +39,12 @@ class AccountDetailsFragment :
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(student: Student) =
|
||||
AccountDetailsFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
|
||||
}
|
||||
fun newInstance(student: Student) = AccountDetailsFragment().apply {
|
||||
arguments = bundleOf(ARGUMENT_KEY to student)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
@ -51,7 +53,7 @@ class AccountDetailsFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
|
@ -4,11 +4,13 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -24,12 +26,9 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
|
||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||
|
||||
fun newInstance(student: Student) =
|
||||
AccountEditDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_KEY, student)
|
||||
}
|
||||
}
|
||||
fun newInstance(student: Student) = AccountEditDialog().apply {
|
||||
arguments = bundleOf(ARGUMENT_KEY to student)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -45,7 +44,7 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
@ -13,6 +14,7 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -30,9 +32,7 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
|
||||
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
|
||||
AccountQuickDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
|
||||
}
|
||||
arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val studentsWithSemesters =
|
||||
(requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList()
|
||||
val studentsWithSemesters = requireArguments()
|
||||
.serializable<Array<StudentWithSemesters>>(STUDENTS_ARGUMENT_KEY).toList()
|
||||
|
||||
presenter.onAttachView(this, studentsWithSemesters)
|
||||
}
|
||||
|
@ -4,11 +4,13 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.databinding.DialogAttendanceBinding
|
||||
import io.github.wulkanowy.utils.descriptionRes
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
|
||||
class AttendanceDialog : DialogFragment() {
|
||||
@ -22,16 +24,14 @@ class AttendanceDialog : DialogFragment() {
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Attendance) = AttendanceDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
arguments = bundleOf(ARGUMENT_KEY to exam)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
attendance = getSerializable(ARGUMENT_KEY) as Attendance
|
||||
}
|
||||
attendance = requireArguments().serializable(ARGUMENT_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -84,6 +84,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -4,11 +4,13 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.databinding.DialogConferenceBinding
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
|
||||
class ConferenceDialog : DialogFragment() {
|
||||
@ -22,16 +24,14 @@ class ConferenceDialog : DialogFragment() {
|
||||
private const val ARGUMENT_KEY = "item"
|
||||
|
||||
fun newInstance(conference: Conference) = ConferenceDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
|
||||
arguments = bundleOf(ARGUMENT_KEY to conference)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.let {
|
||||
conference = it.getSerializable(ARGUMENT_KEY) as Conference
|
||||
}
|
||||
conference = requireArguments().serializable(ARGUMENT_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@ -57,4 +57,4 @@ class ConferenceDialog : DialogFragment() {
|
||||
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
fun newInstance() = DashboardFragment()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -33,18 +33,27 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
data class HorizontalGroup(
|
||||
val unreadMessagesCount: Int? = null,
|
||||
val attendancePercentage: Double? = null,
|
||||
val luckyNumber: Int? = null,
|
||||
val unreadMessagesCount: Cell<Int?>? = null,
|
||||
val attendancePercentage: Cell<Double>? = null,
|
||||
val luckyNumber: Cell<Int>? = null,
|
||||
override val error: Throwable? = null,
|
||||
override val isLoading: Boolean = false
|
||||
) : DashboardItem(Type.HORIZONTAL_GROUP) {
|
||||
|
||||
data class Cell<T>(
|
||||
val data: T?,
|
||||
val error: Boolean,
|
||||
val isLoading: Boolean,
|
||||
) {
|
||||
val isHidden: Boolean
|
||||
get() = data == null && !error && !isLoading
|
||||
}
|
||||
|
||||
override val isDataLoaded
|
||||
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
|
||||
get() = unreadMessagesCount?.isLoading == false || attendancePercentage?.isLoading == false || luckyNumber?.isLoading == false
|
||||
|
||||
val isFullDataLoaded
|
||||
get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1
|
||||
get() = luckyNumber?.isLoading != true && attendancePercentage?.isLoading != true && unreadMessagesCount?.isLoading != true
|
||||
}
|
||||
|
||||
data class Grades(
|
||||
|
@ -25,7 +25,6 @@ class DashboardPresenter @Inject constructor(
|
||||
private val gradeRepository: GradeRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val mailboxRepository: MailboxRepository,
|
||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||
private val timetableRepository: TimetableRepository,
|
||||
private val homeworkRepository: HomeworkRepository,
|
||||
@ -227,50 +226,71 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
||||
|
||||
val flowSuccess = flowOf(Resource.Success(null))
|
||||
|
||||
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
.mapResourceData {
|
||||
it ?: LuckyNumber(0, LocalDate.now(), 0)
|
||||
}
|
||||
.onResourceError { errorHandler.dispatch(it) }
|
||||
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess
|
||||
|
||||
val messageFLow = messageRepository.getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = MessageFolder.RECEIVED,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
||||
val messageFLow = flatResourceFlow {
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
|
||||
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = -1,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
|
||||
messageRepository.getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = MessageFolder.RECEIVED,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
.onResourceError { errorHandler.dispatch(it) }
|
||||
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
||||
|
||||
val attendanceFlow = flatResourceFlow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = -1,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
.onResourceError { errorHandler.dispatch(it) }
|
||||
.takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
|
||||
|
||||
emitAll(
|
||||
combine(
|
||||
luckyNumberFlow,
|
||||
messageFLow,
|
||||
attendanceFlow
|
||||
flow = luckyNumberFlow,
|
||||
flow2 = messageFLow,
|
||||
flow3 = attendanceFlow,
|
||||
) { luckyNumberResource, messageResource, attendanceResource ->
|
||||
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
|
||||
resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it }
|
||||
val isLoading = resList.any { it is Resource.Loading }
|
||||
|
||||
val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber
|
||||
val messageCount = messageResource.dataOrNull?.count { it.unread }
|
||||
val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage()
|
||||
|
||||
DashboardItem.HorizontalGroup(
|
||||
isLoading = isLoading,
|
||||
attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage,
|
||||
unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount,
|
||||
luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber
|
||||
isLoading = resList.any { it is Resource.Loading },
|
||||
error = resList.map { it.errorOrNull }.let { errors ->
|
||||
if (errors.all { it != null }) {
|
||||
errors.firstOrNull()
|
||||
} else null
|
||||
},
|
||||
attendancePercentage = DashboardItem.HorizontalGroup.Cell(
|
||||
data = attendanceResource.dataOrNull?.calculatePercentage(),
|
||||
error = attendanceResource.errorOrNull != null,
|
||||
isLoading = attendanceResource is Resource.Loading,
|
||||
),
|
||||
unreadMessagesCount = DashboardItem.HorizontalGroup.Cell(
|
||||
data = messageResource.dataOrNull?.count { it.unread },
|
||||
error = messageResource.errorOrNull != null,
|
||||
isLoading = messageResource is Resource.Loading,
|
||||
),
|
||||
luckyNumber = DashboardItem.HorizontalGroup.Cell(
|
||||
data = luckyNumberResource.dataOrNull?.luckyNumber,
|
||||
error = luckyNumberResource.errorOrNull != null,
|
||||
isLoading = luckyNumberResource is Resource.Loading,
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -281,11 +301,8 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
if (it.isLoading) {
|
||||
Timber.i("Loading horizontal group data started")
|
||||
|
||||
if (it.isFullDataLoaded) {
|
||||
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
|
||||
}
|
||||
} else {
|
||||
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
|
||||
Timber.i("Loading horizontal group result: Success")
|
||||
}
|
||||
}
|
||||
|
@ -171,81 +171,105 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
position: Int
|
||||
) {
|
||||
val item = items[position] as DashboardItem.HorizontalGroup
|
||||
val unreadMessagesCount = item.unreadMessagesCount
|
||||
val attendancePercentage = item.attendancePercentage
|
||||
val luckyNumber = item.luckyNumber
|
||||
val error = item.error
|
||||
val isLoading = item.isLoading
|
||||
val binding = horizontalGroupViewHolder.binding
|
||||
val context = binding.root.context
|
||||
val isLoadingVisible =
|
||||
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
|
||||
(item.isLoading && !item.isDataLoaded) || (item.isLoading && !item.isFullDataLoaded)
|
||||
val isWideErrorShow = isLoadingVisible || item.error != null
|
||||
|
||||
with(horizontalGroupViewHolder.binding) {
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = isWideErrorShow
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
|
||||
dashboardHorizontalGroupItemInfoErrorText.isVisible = item.error != null
|
||||
|
||||
bindLuckyNumber(item, isWideErrorShow)
|
||||
bindMessages(item, isWideErrorShow)
|
||||
bindAttendance(item, isWideErrorShow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ItemDashboardHorizontalGroupBinding.bindLuckyNumber(
|
||||
item: DashboardItem.HorizontalGroup,
|
||||
isWideErrorShow: Boolean
|
||||
) {
|
||||
with(dashboardHorizontalGroupItemLuckyValue) {
|
||||
isVisible = item.luckyNumber?.error != true
|
||||
text = if (item.luckyNumber?.data == 0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else item.luckyNumber?.data?.toString()
|
||||
}
|
||||
dashboardHorizontalGroupItemLuckyError.isVisible = item.luckyNumber?.error == true
|
||||
with(dashboardHorizontalGroupItemLuckyContainer) {
|
||||
isVisible = item.luckyNumber?.isHidden == false && !isWideErrorShow
|
||||
setOnClickListener { onLuckyNumberTileClickListener() }
|
||||
|
||||
val isAttendanceHidden = item.attendancePercentage?.isHidden == true
|
||||
val isMessagesHidden = item.unreadMessagesCount?.isHidden == true
|
||||
val isLuckyNumberHidden = item.luckyNumber?.isHidden == true
|
||||
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
updateMarginsRelative(
|
||||
end = if (isAttendanceHidden && isMessagesHidden && !isLuckyNumberHidden) {
|
||||
0
|
||||
} else context.dpToPx(8f).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ItemDashboardHorizontalGroupBinding.bindMessages(
|
||||
item: DashboardItem.HorizontalGroup,
|
||||
isWideErrorShow: Boolean
|
||||
) {
|
||||
dashboardHorizontalGroupItemMessageError.isVisible = item.unreadMessagesCount?.error == true
|
||||
with(dashboardHorizontalGroupItemMessageValue) {
|
||||
isVisible = item.unreadMessagesCount?.error != true
|
||||
text = item.unreadMessagesCount?.data.toString()
|
||||
}
|
||||
with(dashboardHorizontalGroupItemMessageContainer) {
|
||||
isVisible = item.unreadMessagesCount?.isHidden == false && !isWideErrorShow
|
||||
setOnClickListener { onMessageTileClickListener() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun ItemDashboardHorizontalGroupBinding.bindAttendance(
|
||||
item: DashboardItem.HorizontalGroup,
|
||||
isWideErrorShow: Boolean
|
||||
) {
|
||||
val attendancePercentage = item.attendancePercentage?.data
|
||||
val attendanceColor = when {
|
||||
attendancePercentage == null || attendancePercentage == .0 -> {
|
||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
root.context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
}
|
||||
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorTimetableChange)
|
||||
root.context.getThemeAttrColor(R.attr.colorTimetableChange)
|
||||
}
|
||||
else -> context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
root.context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else {
|
||||
"%.2f%%".format(attendancePercentage)
|
||||
}
|
||||
|
||||
with(binding.dashboardHorizontalGroupItemAttendanceValue) {
|
||||
dashboardHorizontalGroupItemAttendanceError.isVisible =
|
||||
item.attendancePercentage?.error == true
|
||||
with(dashboardHorizontalGroupItemAttendanceValue) {
|
||||
isVisible = item.attendancePercentage?.error != true
|
||||
text = attendanceString
|
||||
setTextColor(attendanceColor)
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
|
||||
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else luckyNumber?.toString()
|
||||
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
|
||||
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
|
||||
|
||||
with(dashboardHorizontalGroupItemLuckyContainer) {
|
||||
isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible
|
||||
setOnClickListener { onLuckyNumberTileClickListener() }
|
||||
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
updateMarginsRelative(
|
||||
end = if (attendancePercentage == null && unreadMessagesCount == null && luckyNumber != null) {
|
||||
0
|
||||
} else {
|
||||
context.dpToPx(8f).toInt()
|
||||
}
|
||||
)
|
||||
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
||||
isVisible = item.attendancePercentage?.isHidden == false && !isWideErrorShow
|
||||
setOnClickListener { onAttendanceTileClickListener() }
|
||||
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
matchConstraintPercentWidth = when {
|
||||
item.luckyNumber?.isHidden == true && item.unreadMessagesCount?.isHidden == true -> 1.0f
|
||||
item.luckyNumber?.isHidden == true || item.unreadMessagesCount?.isHidden == true -> 0.5f
|
||||
else -> 0.4f
|
||||
}
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
||||
isVisible =
|
||||
attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible
|
||||
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
matchConstraintPercentWidth = when {
|
||||
luckyNumber == null && unreadMessagesCount == null -> 1.0f
|
||||
luckyNumber == null || unreadMessagesCount == null -> 0.5f
|
||||
else -> 0.4f
|
||||
}
|
||||
}
|
||||
setOnClickListener { onAttendanceTileClickListener() }
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemMessageContainer) {
|
||||
isVisible =
|
||||
unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible
|
||||
setOnClickListener { onMessageTileClickListener() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.debug.logviewer
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.EXTRA_EMAIL
|
||||
import android.content.Intent.EXTRA_STREAM
|
||||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.content.Intent.*
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@ -36,6 +34,7 @@ class LogViewerFragment : BaseFragment<FragmentLogviewerBinding>(R.layout.fragme
|
||||
fun newInstance() = LogViewerFragment()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -19,9 +19,12 @@ val debugMessageItems = listOf(
|
||||
private fun generateMessage(sender: String, subject: String) = Message(
|
||||
subject = subject,
|
||||
messageId = 123,
|
||||
email = "",
|
||||
date = Instant.now(),
|
||||
folderId = 0,
|
||||
unread = true,
|
||||
readBy = 2,
|
||||
unreadBy = 2,
|
||||
hasAttachments = false,
|
||||
messageGlobalKey = "",
|
||||
correspondents = sender,
|
||||
|
@ -4,12 +4,14 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.databinding.DialogExamBinding
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.openCalendarEventAdd
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalTime
|
||||
|
||||
@ -24,16 +26,14 @@ class ExamDialog : DialogFragment() {
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(exam: Exam) = ExamDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
arguments = bundleOf(ARGUMENT_KEY to exam)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
exam = getSerializable(ARGUMENT_KEY) as Exam
|
||||
}
|
||||
exam = requireArguments().serializable(ARGUMENT_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -51,6 +51,7 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
||||
|
||||
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
@ -27,22 +28,19 @@ class GradeDetailsDialog : DialogFragment() {
|
||||
|
||||
private const val COLOR_THEME_KEY = "Theme"
|
||||
|
||||
fun newInstance(grade: Grade, colorTheme: GradeColorTheme) =
|
||||
GradeDetailsDialog().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_KEY, grade)
|
||||
putSerializable(COLOR_THEME_KEY, colorTheme)
|
||||
}
|
||||
}
|
||||
fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply {
|
||||
arguments = bundleOf(
|
||||
ARGUMENT_KEY to grade,
|
||||
COLOR_THEME_KEY to colorTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
grade = getSerializable(ARGUMENT_KEY) as Grade
|
||||
gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
|
||||
}
|
||||
grade = requireArguments().serializable(ARGUMENT_KEY)
|
||||
gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -5,9 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.View.*
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -42,6 +40,7 @@ class GradeDetailsFragment :
|
||||
override val isViewEmpty
|
||||
get() = gradeDetailsAdapter.itemCount == 0
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -48,8 +49,8 @@ class GradeStatisticsFragment :
|
||||
messageContainer = binding.gradeStatisticsRecycler
|
||||
presenter.onAttachView(
|
||||
view = this,
|
||||
type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType,
|
||||
subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String,
|
||||
type = savedInstanceState?.serializable(SAVED_CHART_TYPE),
|
||||
subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
|
||||
import io.github.wulkanowy.utils.calcFinalAverage
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
@ -61,7 +62,7 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
if (items.isEmpty()) return
|
||||
|
||||
val context = binding.root.context
|
||||
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
||||
val finalItemsCount = items.count { isGradeValid(it.finalGrade) }
|
||||
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
||||
val finalAverage = items.calcFinalAverage(
|
||||
|
@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -14,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.databinding.DialogHomeworkBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -35,16 +37,14 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
|
||||
private const val ARGUMENT_KEY = "Item"
|
||||
|
||||
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) }
|
||||
arguments = bundleOf(ARGUMENT_KEY to homework)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
homework = getSerializable(ARGUMENT_KEY) as Homework
|
||||
}
|
||||
homework = requireArguments().serializable(ARGUMENT_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -2,13 +2,17 @@ package io.github.wulkanowy.ui.modules.login
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build.VERSION_CODES.TIRAMISU
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
|
||||
import androidx.fragment.app.commit
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.databinding.ActivityLoginBinding
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
|
||||
@ -16,6 +20,9 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
|
||||
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
|
||||
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
|
||||
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.UpdateHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -28,6 +35,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
@Inject
|
||||
lateinit var updateHelper: UpdateHelper
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
companion object {
|
||||
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
|
||||
}
|
||||
@ -55,7 +65,7 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) onBackPressed()
|
||||
if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -67,8 +77,24 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
openFragment(LoginSymbolFragment.newInstance(loginData))
|
||||
}
|
||||
|
||||
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
|
||||
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
|
||||
openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser))
|
||||
}
|
||||
|
||||
fun navigateToNotifications() {
|
||||
val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU
|
||||
val isPermissionGranted = ContextCompat.checkSelfPermission(
|
||||
this, "android.permission.POST_NOTIFICATIONS"
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (isNotificationsPermissionRequired && !isPermissionGranted) {
|
||||
openFragment(NotificationsFragment.newInstance(), clearBackStack = true)
|
||||
} else navigateToFinish()
|
||||
}
|
||||
|
||||
fun navigateToFinish() {
|
||||
startActivity(MainActivity.getStartIntent(this))
|
||||
finish()
|
||||
}
|
||||
|
||||
fun onAdvancedLoginClick() {
|
||||
@ -80,6 +106,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
}
|
||||
|
||||
private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) {
|
||||
supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE)
|
||||
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.loginContainer, fragment)
|
||||
setReorderingAllowed(true)
|
||||
|
@ -6,4 +6,5 @@ data class LoginData(
|
||||
val login: String,
|
||||
val password: String,
|
||||
val baseUrl: String,
|
||||
val symbol: String?,
|
||||
) : Serializable
|
||||
|
@ -8,7 +8,7 @@ import android.widget.ArrayAdapter
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
@ -327,8 +327,8 @@ class LoginAdvancedFragment :
|
||||
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
|
||||
}
|
||||
|
||||
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
|
||||
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -4,9 +4,15 @@ import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.logResourceStatus
|
||||
import io.github.wulkanowy.data.onResourceNotLoading
|
||||
import io.github.wulkanowy.data.pojos.RegisterStudent
|
||||
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
||||
import io.github.wulkanowy.data.pojos.RegisterUnit
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.scrapper.Scrapper
|
||||
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
@ -142,19 +148,23 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
is Resource.Success -> {
|
||||
analytics.logEvent(
|
||||
"registration_form",
|
||||
"success" to true,
|
||||
"students" to it.data.size,
|
||||
"error" to "No error"
|
||||
)
|
||||
val loginData = LoginData(
|
||||
login = view?.formUsernameValue.orEmpty().trim(),
|
||||
password = view?.formPassValue.orEmpty().trim(),
|
||||
baseUrl = view?.formHostValue.orEmpty().trim()
|
||||
)
|
||||
when (it.data.size) {
|
||||
0 -> view?.navigateToSymbol(loginData)
|
||||
else -> view?.navigateToStudentSelect(it.data)
|
||||
}
|
||||
"success" to true,
|
||||
"students" to it.data.size,
|
||||
"error" to "No error"
|
||||
)
|
||||
val loginData = LoginData(
|
||||
login = view?.formUsernameValue.orEmpty().trim(),
|
||||
password = view?.formPassValue.orEmpty().trim(),
|
||||
baseUrl = view?.formHostValue.orEmpty().trim(),
|
||||
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
|
||||
)
|
||||
when (it.data.size) {
|
||||
0 -> view?.navigateToSymbol(loginData)
|
||||
else -> view?.navigateToStudentSelect(
|
||||
loginData = loginData,
|
||||
registerUser = it.data.toRegisterUser(loginData),
|
||||
)
|
||||
}
|
||||
}
|
||||
is Resource.Error -> {
|
||||
analytics.logEvent(
|
||||
@ -173,6 +183,58 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
}.launch("login")
|
||||
}
|
||||
|
||||
private fun List<StudentWithSemesters>.toRegisterUser(loginData: LoginData) = RegisterUser(
|
||||
email = loginData.login,
|
||||
password = loginData.password,
|
||||
login = loginData.login,
|
||||
baseUrl = loginData.baseUrl,
|
||||
loginType = firstOrNull()?.student?.loginType?.let(
|
||||
Scrapper.LoginType::valueOf
|
||||
) ?: Scrapper.LoginType.AUTO,
|
||||
symbols = this
|
||||
.groupBy { students -> students.student.symbol }
|
||||
.map { (symbol, students) ->
|
||||
RegisterSymbol(
|
||||
symbol = symbol,
|
||||
error = null,
|
||||
userName = "",
|
||||
schools = students
|
||||
.groupBy { student ->
|
||||
Triple(
|
||||
first = student.student.schoolSymbol,
|
||||
second = student.student.userLoginId,
|
||||
third = student.student.schoolShortName
|
||||
)
|
||||
}
|
||||
.map { (groupKey, students) ->
|
||||
val (schoolId, loginId, schoolName) = groupKey
|
||||
RegisterUnit(
|
||||
students = students.map {
|
||||
RegisterStudent(
|
||||
studentId = it.student.studentId,
|
||||
studentName = it.student.studentName,
|
||||
studentSecondName = it.student.studentName,
|
||||
studentSurname = it.student.studentName,
|
||||
className = it.student.className,
|
||||
classId = it.student.classId,
|
||||
isParent = it.student.isParent,
|
||||
semesters = it.semesters,
|
||||
)
|
||||
},
|
||||
userLoginId = loginId,
|
||||
schoolId = schoolId,
|
||||
schoolName = schoolName,
|
||||
schoolShortName = schoolName,
|
||||
parentIds = listOf(),
|
||||
studentIds = listOf(),
|
||||
employeeIds = listOf(),
|
||||
error = null
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> {
|
||||
val email = view?.formUsernameValue.orEmpty()
|
||||
val password = view?.formPassValue.orEmpty()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.login.advanced
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
|
||||
@ -72,7 +73,7 @@ interface LoginAdvancedView : BaseView {
|
||||
|
||||
fun navigateToSymbol(loginData: LoginData)
|
||||
|
||||
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
|
||||
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
|
||||
|
||||
fun setErrorPinRequired()
|
||||
|
||||
|
@ -9,7 +9,8 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
@ -32,6 +33,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
fun newInstance() = LoginFormFragment()
|
||||
}
|
||||
@ -222,8 +226,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
|
||||
}
|
||||
|
||||
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
|
||||
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
|
||||
}
|
||||
|
||||
override fun openAdvancedLogin() {
|
||||
@ -260,8 +264,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
appInfo.versionName,
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"$formHostValue/$formHostSymbol",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
if (!validateCredentials(email, password, host)) return
|
||||
|
||||
resourceFlow {
|
||||
studentRepository.getStudentsScrapper(
|
||||
studentRepository.getUserSubjectsFromScrapper(
|
||||
email = email,
|
||||
password = password,
|
||||
scrapperBaseUrl = host,
|
||||
@ -109,14 +109,14 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
.onResourceSuccess {
|
||||
when (it.size) {
|
||||
0 -> view?.navigateToSymbol(LoginData(email, password, host))
|
||||
else -> view?.navigateToStudentSelect(it)
|
||||
val loginData = LoginData(email, password, host, symbol)
|
||||
when (it.symbols.size) {
|
||||
0 -> view?.navigateToSymbol(loginData)
|
||||
else -> view?.navigateToStudentSelect(loginData, it)
|
||||
}
|
||||
analytics.logEvent(
|
||||
"registration_form",
|
||||
"success" to true,
|
||||
"students" to it.size,
|
||||
"scrapperBaseUrl" to host,
|
||||
"error" to "No error"
|
||||
)
|
||||
@ -134,7 +134,6 @@ class LoginFormPresenter @Inject constructor(
|
||||
analytics.logEvent(
|
||||
"registration_form",
|
||||
"success" to false,
|
||||
"students" to -1,
|
||||
"scrapperBaseUrl" to host,
|
||||
"error" to it.message.ifNullOrBlank { "No message" }
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.login.form
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
|
||||
@ -60,7 +60,7 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun navigateToSymbol(loginData: LoginData)
|
||||
|
||||
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
|
||||
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
|
||||
|
||||
fun openPrivacyPolicyPage()
|
||||
|
||||
|
@ -98,7 +98,7 @@ class LoginRecoverFragment :
|
||||
loginRecoverButton.setOnClickListener { presenter.onRecoverClick() }
|
||||
loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() }
|
||||
loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() }
|
||||
loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() }
|
||||
}
|
||||
|
||||
with(bindingLocal.loginRecoverHost) {
|
||||
|
@ -2,65 +2,182 @@ package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
class LoginStudentSelectAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
|
||||
ListAdapter<LoginStudentSelectItem, RecyclerView.ViewHolder>(Differ) {
|
||||
|
||||
private val checkedList = mutableMapOf<Int, Boolean>()
|
||||
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
|
||||
|
||||
var items = emptyList<Pair<StudentWithSemesters, Boolean>>()
|
||||
set(value) {
|
||||
field = value
|
||||
checkedList.clear()
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (LoginStudentSelectItemType.values()[viewType]) {
|
||||
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
|
||||
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
|
||||
)
|
||||
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
|
||||
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
|
||||
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
|
||||
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
LoginStudentSelectItemType.HELP -> HelpViewHolder(
|
||||
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var onClickListener: (StudentWithSemesters, alreadySaved: Boolean) -> Unit = { _, _ -> }
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader)
|
||||
is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader)
|
||||
is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader)
|
||||
is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student)
|
||||
is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
private class EmptySymbolsHeaderViewHolder(
|
||||
private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val (studentAndSemesters, alreadySaved) = items[position]
|
||||
val student = studentAndSemesters.student
|
||||
val semesters = studentAndSemesters.semesters
|
||||
val diary = semesters.maxByOrNull { it.semesterId }
|
||||
|
||||
with(holder.binding) {
|
||||
loginItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
|
||||
loginItemSchool.text = student.schoolName
|
||||
loginItemName.isEnabled = !alreadySaved
|
||||
loginItemSchool.isEnabled = !alreadySaved
|
||||
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
|
||||
|
||||
with(loginItemCheck) {
|
||||
isEnabled = !alreadySaved
|
||||
keyListener = null
|
||||
isChecked = checkedList[position] ?: false
|
||||
fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) {
|
||||
with(binding) {
|
||||
loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f
|
||||
root.setOnClickListener { item.onClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
onClickListener(studentAndSemesters, alreadySaved)
|
||||
private class SymbolsHeaderViewHolder(
|
||||
private val binding: ItemLoginStudentSelectHeaderSymbolBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: LoginStudentSelectItem.SymbolHeader) {
|
||||
with(binding) {
|
||||
loginStudentSelectHeaderSymbolValue.text = buildString {
|
||||
append(root.context.getString(R.string.mobile_device_symbol))
|
||||
append(": ")
|
||||
append(item.humanReadableName ?: item.symbol.symbol)
|
||||
if (!item.humanReadableName.isNullOrBlank()) {
|
||||
append(" (${item.symbol.symbol})")
|
||||
}
|
||||
}
|
||||
loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName
|
||||
loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank()
|
||||
loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message
|
||||
loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null
|
||||
loginStudentSelectHeaderSymbolError.maxLines = when {
|
||||
item.isErrorExpanded -> Int.MAX_VALUE
|
||||
else -> 2
|
||||
}
|
||||
|
||||
if (item.symbol.error != null) {
|
||||
root.setOnClickListener { item.onClick(item.symbol) }
|
||||
} else root.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SchoolHeaderViewHolder(
|
||||
private val binding: ItemLoginStudentSelectHeaderSchoolBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: LoginStudentSelectItem.SchoolHeader) {
|
||||
with(binding) {
|
||||
loginStudentSelectHeaderSchoolName.text = buildString {
|
||||
append(item.unit.schoolName.trim())
|
||||
append(" (")
|
||||
append(item.unit.schoolShortName)
|
||||
append(")")
|
||||
}
|
||||
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
|
||||
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
|
||||
loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
|
||||
loginStudentSelectHeaderSchoolError.maxLines = when {
|
||||
item.isErrorExpanded -> Int.MAX_VALUE
|
||||
else -> 2
|
||||
}
|
||||
|
||||
if (item.unit.error != null) {
|
||||
root.setOnClickListener { item.onClick(item.unit) }
|
||||
} else root.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StudentViewHolder(
|
||||
private val binding: ItemLoginStudentSelectStudentBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: LoginStudentSelectItem.Student) {
|
||||
val student = item.student
|
||||
val semesters = student.semesters
|
||||
val diary = semesters.maxByOrNull { it.semesterId }
|
||||
|
||||
with(binding) {
|
||||
loginItemName.text = "${student.studentName} ${student.studentSurname}"
|
||||
loginItemName.isEnabled = item.isEnabled
|
||||
loginItemSignedIn.text = if (!item.isEnabled) {
|
||||
root.context.getString(R.string.login_signed_in)
|
||||
} else diary?.diaryName
|
||||
|
||||
with(loginItemCheck) {
|
||||
if (isEnabled) {
|
||||
isChecked = !isChecked
|
||||
checkedList[position] = isChecked
|
||||
}
|
||||
keyListener = null
|
||||
isEnabled = item.isEnabled
|
||||
isChecked = item.isSelected || !item.isEnabled
|
||||
}
|
||||
|
||||
root.isEnabled = item.isEnabled
|
||||
root.setOnClickListener {
|
||||
item.onClick(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
private class HelpViewHolder(
|
||||
private val binding: ItemLoginStudentSelectHelpBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: LoginStudentSelectItem.Help) {
|
||||
with(binding) {
|
||||
loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible
|
||||
loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() }
|
||||
loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() }
|
||||
loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object Differ : ItemCallback<LoginStudentSelectItem>() {
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
|
||||
): Boolean = when {
|
||||
oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true
|
||||
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
|
||||
oldItem.symbol == newItem.symbol
|
||||
}
|
||||
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
|
||||
oldItem.student == newItem.student
|
||||
}
|
||||
else -> oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,20 @@ package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -32,12 +32,26 @@ class LoginStudentSelectFragment :
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
companion object {
|
||||
const val ARG_STUDENTS = "STUDENTS"
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
|
||||
private lateinit var symbolsNames: Array<String>
|
||||
private lateinit var symbolsValues: Array<String>
|
||||
|
||||
override val symbols: Map<String, String> by lazy {
|
||||
symbolsValues.zip(symbolsNames).toMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_LOGIN = "LOGIN"
|
||||
private const val ARG_STUDENTS = "STUDENTS"
|
||||
|
||||
fun newInstance(loginData: LoginData, registerUser: RegisterUser) =
|
||||
LoginStudentSelectFragment().apply {
|
||||
arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters)
|
||||
arguments = bundleOf(
|
||||
ARG_LOGIN to loginData,
|
||||
ARG_STUDENTS to registerUser,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,62 +59,50 @@ class LoginStudentSelectFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentLoginStudentSelectBinding.bind(view)
|
||||
|
||||
symbolsNames = resources.getStringArray(R.array.symbols)
|
||||
symbolsValues = resources.getStringArray(R.array.symbols_values)
|
||||
|
||||
presenter.onAttachView(
|
||||
view = this,
|
||||
students = requireArguments().getSerializable(ARG_STUDENTS) as List<StudentWithSemesters>,
|
||||
loginData = requireArguments().serializable(ARG_LOGIN),
|
||||
registerUser = requireArguments().serializable(ARG_STUDENTS),
|
||||
)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
(requireActivity() as LoginActivity).showActionBar(true)
|
||||
|
||||
loginAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
|
||||
loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
|
||||
loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
|
||||
|
||||
with(loginStudentSelectRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = loginAdapter
|
||||
}
|
||||
loginStudentSelectRecycler.adapter = loginAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>) {
|
||||
with(loginAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
override fun updateData(data: List<LoginStudentSelectItem>) {
|
||||
loginAdapter.submitList(data)
|
||||
}
|
||||
|
||||
override fun openMainView() {
|
||||
startActivity(MainActivity.getStartIntent(requireContext()))
|
||||
requireActivity().finish()
|
||||
override fun navigateToSymbol(loginData: LoginData) {
|
||||
(requireActivity() as LoginActivity).navigateToSymbolFragment(loginData)
|
||||
}
|
||||
|
||||
override fun navigateToNext() {
|
||||
(requireActivity() as LoginActivity).navigateToNotifications()
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE
|
||||
binding.loginStudentSelectProgress.isVisible = show
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE
|
||||
binding.loginStudentSelectContent.isVisible = show
|
||||
}
|
||||
|
||||
override fun enableSignIn(enable: Boolean) {
|
||||
binding.loginStudentSelectSignIn.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showContact(show: Boolean) {
|
||||
binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun openDiscordInvite() {
|
||||
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
|
||||
}
|
||||
@ -111,12 +113,19 @@ class LoginStudentSelectFragment :
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text, appInfo.systemModel,
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
appInfo.versionName,
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"Select users to log in",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import io.github.wulkanowy.data.pojos.RegisterStudent
|
||||
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
||||
import io.github.wulkanowy.data.pojos.RegisterUnit
|
||||
|
||||
sealed class LoginStudentSelectItem(val type: LoginStudentSelectItemType) {
|
||||
|
||||
data class EmptySymbolsHeader(
|
||||
val isExpanded: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
) : LoginStudentSelectItem(LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER)
|
||||
|
||||
data class SymbolHeader(
|
||||
val symbol: RegisterSymbol,
|
||||
val humanReadableName: String?,
|
||||
val isErrorExpanded: Boolean,
|
||||
val onClick: (RegisterSymbol) -> Unit,
|
||||
) : LoginStudentSelectItem(LoginStudentSelectItemType.SYMBOL_HEADER)
|
||||
|
||||
data class SchoolHeader(
|
||||
val unit: RegisterUnit,
|
||||
val isErrorExpanded: Boolean,
|
||||
val onClick: (RegisterUnit) -> Unit,
|
||||
) : LoginStudentSelectItem(LoginStudentSelectItemType.SCHOOL_HEADER)
|
||||
|
||||
data class Student(
|
||||
val symbol: RegisterSymbol,
|
||||
val unit: RegisterUnit,
|
||||
val student: RegisterStudent,
|
||||
val isEnabled: Boolean,
|
||||
val isSelected: Boolean,
|
||||
val onClick: (Student) -> Unit,
|
||||
) : LoginStudentSelectItem(LoginStudentSelectItemType.STUDENT)
|
||||
|
||||
data class Help(
|
||||
val onEnterSymbolClick: () -> Unit,
|
||||
val onContactUsClick: () -> Unit,
|
||||
val onDiscordClick: () -> Unit,
|
||||
val isSymbolButtonVisible: Boolean,
|
||||
) : LoginStudentSelectItem(LoginStudentSelectItemType.HELP)
|
||||
}
|
||||
|
||||
enum class LoginStudentSelectItemType {
|
||||
EMPTY_SYMBOLS_HEADER,
|
||||
SYMBOL_HEADER,
|
||||
SCHOOL_HEADER,
|
||||
STUDENT,
|
||||
HELP,
|
||||
}
|
@ -1,15 +1,23 @@
|
||||
package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.logResourceStatus
|
||||
import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterStudent
|
||||
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
||||
import io.github.wulkanowy.data.pojos.RegisterUnit
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
@ -19,18 +27,30 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
private val loginErrorHandler: LoginErrorHandler,
|
||||
private val syncManager: SyncManager,
|
||||
private val analytics: AnalyticsHelper
|
||||
private val analytics: AnalyticsHelper,
|
||||
private val appInfo: AppInfo,
|
||||
) : BasePresenter<LoginStudentSelectView>(loginErrorHandler, studentRepository) {
|
||||
|
||||
private var lastError: Throwable? = null
|
||||
|
||||
private val selectedStudents = mutableListOf<StudentWithSemesters>()
|
||||
private lateinit var registerUser: RegisterUser
|
||||
private lateinit var loginData: LoginData
|
||||
|
||||
fun onAttachView(view: LoginStudentSelectView, students: List<StudentWithSemesters>) {
|
||||
private lateinit var students: List<StudentWithSemesters>
|
||||
private var isEmptySymbolsExpanded = false
|
||||
private var expandedSymbolError: RegisterSymbol? = null
|
||||
private var expandedSchoolError: RegisterUnit? = null
|
||||
|
||||
private val selectedStudents = mutableListOf<LoginStudentSelectItem.Student>()
|
||||
|
||||
fun onAttachView(
|
||||
view: LoginStudentSelectView,
|
||||
loginData: LoginData,
|
||||
registerUser: RegisterUser,
|
||||
) {
|
||||
super.onAttachView(view)
|
||||
with(view) {
|
||||
initView()
|
||||
showContact(false)
|
||||
enableSignIn(false)
|
||||
loginErrorHandler.onStudentDuplicate = {
|
||||
showMessage(it)
|
||||
@ -38,50 +58,171 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (students.size == 1) registerStudents(students)
|
||||
loadData(students)
|
||||
this.loginData = loginData
|
||||
this.registerUser = registerUser
|
||||
loadData()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
resetSelectedState()
|
||||
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
|
||||
students = it.dataOrNull.orEmpty()
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Login student select students load started")
|
||||
is Resource.Success -> refreshItems()
|
||||
is Resource.Error -> {
|
||||
errorHandler.dispatch(it.error)
|
||||
lastError = it.error
|
||||
refreshItems()
|
||||
}
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun createItems(): List<LoginStudentSelectItem> = buildList {
|
||||
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
|
||||
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
|
||||
|
||||
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) {
|
||||
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol }))
|
||||
}
|
||||
|
||||
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
|
||||
addAll(createEmptySymbolItems(emptySymbols, notEmptySymbols.isNotEmpty()))
|
||||
|
||||
val helpItem = LoginStudentSelectItem.Help(
|
||||
onEnterSymbolClick = ::onEnterSymbol,
|
||||
onContactUsClick = ::onEmailClick,
|
||||
onDiscordClick = ::onDiscordClick,
|
||||
isSymbolButtonVisible = "login" !in loginData.baseUrl,
|
||||
)
|
||||
add(helpItem)
|
||||
}
|
||||
|
||||
private fun createNotEmptySymbolItems(
|
||||
notEmptySymbols: List<RegisterSymbol>,
|
||||
students: List<StudentWithSemesters>,
|
||||
) = buildList {
|
||||
notEmptySymbols.forEach { registerSymbol ->
|
||||
val symbolHeader = LoginStudentSelectItem.SymbolHeader(
|
||||
symbol = registerSymbol,
|
||||
humanReadableName = view?.symbols?.get(registerSymbol.symbol),
|
||||
isErrorExpanded = expandedSymbolError == registerSymbol,
|
||||
onClick = ::onSymbolItemClick,
|
||||
)
|
||||
add(symbolHeader)
|
||||
|
||||
registerSymbol.schools.forEach { registerUnit ->
|
||||
val schoolHeader = LoginStudentSelectItem.SchoolHeader(
|
||||
unit = registerUnit,
|
||||
isErrorExpanded = expandedSchoolError == registerUnit,
|
||||
onClick = ::onUnitItemClick,
|
||||
)
|
||||
add(schoolHeader)
|
||||
|
||||
registerUnit.students.forEach {
|
||||
add(createStudentItem(it, registerSymbol, registerUnit, students))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStudentItem(
|
||||
student: RegisterStudent,
|
||||
symbol: RegisterSymbol,
|
||||
school: RegisterUnit,
|
||||
students: List<StudentWithSemesters>,
|
||||
) = LoginStudentSelectItem.Student(
|
||||
symbol = symbol,
|
||||
unit = school,
|
||||
student = student,
|
||||
onClick = ::onItemSelected,
|
||||
isEnabled = students.none {
|
||||
it.student.email == registerUser.login
|
||||
&& it.student.symbol == symbol.symbol
|
||||
&& it.student.studentId == student.studentId
|
||||
&& it.student.schoolSymbol == school.schoolId
|
||||
&& it.student.classId == student.classId
|
||||
},
|
||||
isSelected = selectedStudents
|
||||
.filter { it.symbol.symbol == symbol.symbol }
|
||||
.filter { it.unit.schoolId == school.schoolId }
|
||||
.filter { it.student.studentId == student.studentId }
|
||||
.filter { it.student.classId == student.classId }
|
||||
.size == 1,
|
||||
)
|
||||
|
||||
private fun createEmptySymbolItems(
|
||||
emptySymbols: List<RegisterSymbol>,
|
||||
isNotEmptySymbolsExist: Boolean,
|
||||
) = buildList {
|
||||
val filteredEmptySymbols = emptySymbols.filter {
|
||||
it.error !is AccountPermissionException
|
||||
}.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() }
|
||||
|
||||
if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) {
|
||||
val emptyHeader = LoginStudentSelectItem.EmptySymbolsHeader(
|
||||
isExpanded = isEmptySymbolsExpanded,
|
||||
onClick = ::onEmptySymbolsToggle,
|
||||
)
|
||||
add(emptyHeader)
|
||||
if (isEmptySymbolsExpanded) {
|
||||
filteredEmptySymbols.forEach {
|
||||
add(createEmptySymbolItem(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredEmptySymbols.isNotEmpty() && !isNotEmptySymbolsExist) {
|
||||
filteredEmptySymbols.forEach {
|
||||
add(createEmptySymbolItem(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptySymbolItem(registerSymbol: RegisterSymbol) =
|
||||
LoginStudentSelectItem.SymbolHeader(
|
||||
symbol = registerSymbol,
|
||||
humanReadableName = view?.symbols?.get(registerSymbol.symbol),
|
||||
isErrorExpanded = expandedSymbolError == registerSymbol,
|
||||
onClick = ::onSymbolItemClick,
|
||||
)
|
||||
|
||||
fun onSignIn() {
|
||||
registerStudents(selectedStudents)
|
||||
}
|
||||
|
||||
fun onItemSelected(studentWithSemester: StudentWithSemesters, alreadySaved: Boolean) {
|
||||
if (alreadySaved) return
|
||||
private fun onEmptySymbolsToggle() {
|
||||
isEmptySymbolsExpanded = !isEmptySymbolsExpanded
|
||||
|
||||
refreshItems()
|
||||
}
|
||||
|
||||
private fun onItemSelected(item: LoginStudentSelectItem.Student) {
|
||||
if (!item.isEnabled) return
|
||||
|
||||
selectedStudents
|
||||
.removeAll { it == studentWithSemester }
|
||||
.let { if (!it) selectedStudents.add(studentWithSemester) }
|
||||
.removeAll {
|
||||
it.student.studentId == item.student.studentId &&
|
||||
it.student.classId == item.student.classId &&
|
||||
it.unit.schoolId == item.unit.schoolId &&
|
||||
it.symbol.symbol == item.symbol.symbol
|
||||
}
|
||||
.let { if (!it) selectedStudents.add(item) }
|
||||
|
||||
view?.enableSignIn(selectedStudents.isNotEmpty())
|
||||
refreshItems()
|
||||
}
|
||||
|
||||
private fun compareStudents(a: Student, b: Student): Boolean {
|
||||
return a.email == b.email
|
||||
&& a.symbol == b.symbol
|
||||
&& a.studentId == b.studentId
|
||||
&& a.schoolSymbol == b.schoolSymbol
|
||||
&& a.classId == b.classId
|
||||
private fun onSymbolItemClick(symbol: RegisterSymbol) {
|
||||
expandedSymbolError = if (symbol != expandedSymbolError) symbol else null
|
||||
refreshItems()
|
||||
}
|
||||
|
||||
private fun loadData(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
resetSelectedState()
|
||||
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Login student select students load started")
|
||||
is Resource.Success -> view?.updateData(studentsWithSemesters.map { studentWithSemesters ->
|
||||
studentWithSemesters to it.data.any { item ->
|
||||
compareStudents(studentWithSemesters.student, item.student)
|
||||
}
|
||||
})
|
||||
is Resource.Error -> {
|
||||
errorHandler.dispatch(it.error)
|
||||
lastError = it.error
|
||||
view?.updateData(studentsWithSemesters.map { student -> student to false })
|
||||
}
|
||||
}
|
||||
}.launch()
|
||||
private fun onUnitItemClick(unit: RegisterUnit) {
|
||||
expandedSchoolError = if (unit != expandedSchoolError) unit else null
|
||||
refreshItems()
|
||||
}
|
||||
|
||||
private fun resetSelectedState() {
|
||||
@ -89,7 +230,20 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
view?.enableSignIn(false)
|
||||
}
|
||||
|
||||
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
private fun refreshItems() {
|
||||
view?.updateData(createItems())
|
||||
}
|
||||
|
||||
private fun registerStudents(students: List<LoginStudentSelectItem>) {
|
||||
val studentsWithSemesters = students
|
||||
.filterIsInstance<LoginStudentSelectItem.Student>().map { item ->
|
||||
item.student.mapToStudentWithSemesters(
|
||||
user = registerUser,
|
||||
symbol = item.symbol,
|
||||
unit = item.unit,
|
||||
colors = appInfo.defaultColorsForAvatar,
|
||||
)
|
||||
}
|
||||
resourceFlow { studentRepository.saveStudents(studentsWithSemesters) }
|
||||
.logResourceStatus("registration")
|
||||
.onEach {
|
||||
@ -100,14 +254,13 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
is Resource.Success -> {
|
||||
syncManager.startOneTimeSyncWorker(quiet = true)
|
||||
view?.openMainView()
|
||||
view?.navigateToNext()
|
||||
logRegisterEvent(studentsWithSemesters)
|
||||
}
|
||||
is Resource.Error -> {
|
||||
view?.apply {
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
showContact(true)
|
||||
}
|
||||
lastError = it.error
|
||||
loginErrorHandler.dispatch(it.error)
|
||||
@ -117,12 +270,22 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}.launch("register")
|
||||
}
|
||||
|
||||
fun onDiscordClick() {
|
||||
private fun onEnterSymbol() {
|
||||
view?.navigateToSymbol(loginData)
|
||||
}
|
||||
|
||||
private fun onDiscordClick() {
|
||||
view?.openDiscordInvite()
|
||||
}
|
||||
|
||||
fun onEmailClick() {
|
||||
view?.openEmail(lastError?.message.ifNullOrBlank { "empty" })
|
||||
private fun onEmailClick() {
|
||||
view?.openEmail(lastError?.message.ifNullOrBlank {
|
||||
registerUser.symbols.flatMap { symbol ->
|
||||
symbol.schools.map { it.error?.message } + symbol.error?.message
|
||||
}.filterNotNull().distinct().joinToString("; ") {
|
||||
it.take(46) + "..."
|
||||
}.ifEmpty { "blank" }
|
||||
})
|
||||
}
|
||||
|
||||
private fun logRegisterEvent(
|
||||
|
@ -1,15 +1,19 @@
|
||||
package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
|
||||
interface LoginStudentSelectView : BaseView {
|
||||
|
||||
val symbols: Map<String, String>
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>)
|
||||
fun updateData(data: List<LoginStudentSelectItem>)
|
||||
|
||||
fun openMainView()
|
||||
fun navigateToSymbol(loginData: LoginData)
|
||||
|
||||
fun navigateToNext()
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
@ -17,8 +21,6 @@ interface LoginStudentSelectView : BaseView {
|
||||
|
||||
fun enableSignIn(enable: Boolean)
|
||||
|
||||
fun showContact(show: Boolean)
|
||||
|
||||
fun openDiscordInvite()
|
||||
|
||||
fun openEmail(lastError: String)
|
||||
|
@ -12,16 +12,13 @@ import androidx.core.text.parseAsHtml
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.hideSoftInput
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.showSoftInput
|
||||
import io.github.wulkanowy.utils.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -34,6 +31,9 @@ class LoginSymbolFragment :
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
|
||||
|
||||
@ -42,6 +42,8 @@ class LoginSymbolFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override val symbolValue: String? get() = binding.loginSymbolName.text?.toString()
|
||||
|
||||
override val symbolNameError: CharSequence?
|
||||
get() = binding.loginSymbolNameLayout.error
|
||||
|
||||
@ -50,7 +52,7 @@ class LoginSymbolFragment :
|
||||
binding = FragmentLoginSymbolBinding.bind(view)
|
||||
presenter.onAttachView(
|
||||
view = this,
|
||||
loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData,
|
||||
loginData = requireArguments().serializable(SAVED_LOGIN_DATA),
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,7 +60,7 @@ class LoginSymbolFragment :
|
||||
(requireActivity() as LoginActivity).showActionBar(true)
|
||||
|
||||
with(binding) {
|
||||
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
|
||||
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin() }
|
||||
loginSymbolFaq.setOnClickListener { presenter.onFaqClick() }
|
||||
loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() }
|
||||
|
||||
@ -92,9 +94,13 @@ class LoginSymbolFragment :
|
||||
}
|
||||
|
||||
override fun setErrorSymbolRequire() {
|
||||
binding.loginSymbolNameLayout.apply {
|
||||
setErrorSymbol(getString(R.string.error_field_required))
|
||||
}
|
||||
|
||||
override fun setErrorSymbol(message: String) {
|
||||
with(binding.loginSymbolNameLayout) {
|
||||
requestFocus()
|
||||
error = getString(R.string.error_field_required)
|
||||
error = message
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,8 +131,8 @@ class LoginSymbolFragment :
|
||||
binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
|
||||
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
|
||||
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@ -159,8 +165,9 @@ class LoginSymbolFragment :
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
appInfo.versionName,
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"$host/${binding.loginSymbolName.text}",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
|
@ -1,9 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.login.symbol
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.onResourceNotLoading
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
@ -23,9 +26,14 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
|
||||
lateinit var loginData: LoginData
|
||||
|
||||
private var registerUser: RegisterUser? = null
|
||||
|
||||
fun onAttachView(view: LoginSymbolView, loginData: LoginData) {
|
||||
super.onAttachView(view)
|
||||
this.loginData = loginData
|
||||
loginErrorHandler.onBadCredentials = {
|
||||
view.setErrorSymbol(it.orEmpty())
|
||||
}
|
||||
with(view) {
|
||||
initView()
|
||||
showContact(false)
|
||||
@ -39,20 +47,24 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
view?.apply { if (symbolNameError != null) clearSymbolError() }
|
||||
}
|
||||
|
||||
fun attemptLogin(symbol: String) {
|
||||
if (symbol.isBlank()) {
|
||||
fun attemptLogin() {
|
||||
if (view?.symbolValue.isNullOrBlank()) {
|
||||
view?.setErrorSymbolRequire()
|
||||
return
|
||||
}
|
||||
|
||||
loginData = loginData.copy(
|
||||
symbol = view?.symbolValue?.getNormalizedSymbol(),
|
||||
)
|
||||
resourceFlow {
|
||||
studentRepository.getStudentsScrapper(
|
||||
studentRepository.getUserSubjectsFromScrapper(
|
||||
email = loginData.login,
|
||||
password = loginData.password,
|
||||
scrapperBaseUrl = loginData.baseUrl,
|
||||
symbol = symbol,
|
||||
symbol = view?.symbolValue.orEmpty(),
|
||||
)
|
||||
}.onEach {
|
||||
registerUser = it.dataOrNull
|
||||
when (it) {
|
||||
is Resource.Loading -> view?.run {
|
||||
Timber.i("Login with symbol started")
|
||||
@ -61,7 +73,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
showContent(false)
|
||||
}
|
||||
is Resource.Success -> {
|
||||
when (it.data.size) {
|
||||
when (it.data.symbols.size) {
|
||||
0 -> {
|
||||
Timber.i("Login with symbol result: Empty student list")
|
||||
view?.run {
|
||||
@ -71,15 +83,14 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
}
|
||||
else -> {
|
||||
Timber.i("Login with symbol result: Success")
|
||||
view?.navigateToStudentSelect(requireNotNull(it.data))
|
||||
view?.navigateToStudentSelect(loginData, requireNotNull(it.data))
|
||||
}
|
||||
}
|
||||
analytics.logEvent(
|
||||
"registration_symbol",
|
||||
"success" to true,
|
||||
"students" to it.data.size,
|
||||
"scrapperBaseUrl" to loginData.baseUrl,
|
||||
"symbol" to symbol,
|
||||
"symbol" to view?.symbolValue,
|
||||
"error" to "No error"
|
||||
)
|
||||
}
|
||||
@ -90,7 +101,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
"success" to false,
|
||||
"students" to -1,
|
||||
"scrapperBaseUrl" to loginData.baseUrl,
|
||||
"symbol" to symbol,
|
||||
"symbol" to view?.symbolValue,
|
||||
"error" to it.error.message.ifNullOrBlank { "No message" }
|
||||
)
|
||||
loginErrorHandler.dispatch(it.error)
|
||||
@ -111,6 +122,12 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onEmailClick() {
|
||||
view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { "empty" })
|
||||
view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank {
|
||||
registerUser?.symbols?.flatMap { symbol ->
|
||||
symbol.schools.map { it.error?.message } + symbol.error?.message
|
||||
}?.filterNotNull()?.distinct()?.joinToString(";") {
|
||||
it.take(46) + "..."
|
||||
} ?: "blank"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package io.github.wulkanowy.ui.modules.login.symbol
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
|
||||
interface LoginSymbolView : BaseView {
|
||||
|
||||
val symbolValue: String?
|
||||
|
||||
val symbolNameError: CharSequence?
|
||||
|
||||
fun initView()
|
||||
@ -15,6 +18,8 @@ interface LoginSymbolView : BaseView {
|
||||
|
||||
fun setErrorSymbolRequire()
|
||||
|
||||
fun setErrorSymbol(message: String)
|
||||
|
||||
fun clearSymbolError()
|
||||
|
||||
fun clearAndFocusSymbol()
|
||||
@ -27,7 +32,7 @@ interface LoginSymbolView : BaseView {
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
|
||||
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
|
||||
|
||||
fun showContact(show: Boolean)
|
||||
|
||||
|
@ -6,6 +6,8 @@ import android.os.Build.VERSION_CODES.P
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.addCallback
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.DialogFragment
|
||||
@ -50,6 +52,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
private var onBackCallback: OnBackPressedCallback? = null
|
||||
|
||||
private var accountMenu: MenuItem? = null
|
||||
|
||||
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
||||
@ -88,6 +92,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
this.savedInstanceState = savedInstanceState
|
||||
messageContainer = binding.mainMessageContainer
|
||||
updateHelper.messageContainer = binding.mainFragmentContainer
|
||||
onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) {
|
||||
presenter.onBackPressed()
|
||||
}
|
||||
|
||||
val destination = intent.getStringExtra(EXTRA_START_DESTINATION)
|
||||
?.takeIf { savedInstanceState == null }
|
||||
@ -266,6 +273,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
|
||||
navController.pushFragment(fragment)
|
||||
onBackCallback?.isEnabled = !isRootView
|
||||
}
|
||||
|
||||
override fun popView(depth: Int) {
|
||||
@ -273,10 +281,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
|
||||
navController.safelyPopFragments(depth)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
presenter.onBackPressed { super.onBackPressed() }
|
||||
onBackCallback?.isEnabled = !isRootView
|
||||
}
|
||||
|
||||
override fun showStudentAvatar(student: Student) {
|
||||
|
@ -139,12 +139,9 @@ class MainPresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
fun onBackPressed(default: () -> Unit) {
|
||||
fun onBackPressed() {
|
||||
Timber.i("Back pressed in main view")
|
||||
view?.run {
|
||||
if (isRootView) default()
|
||||
else popView()
|
||||
}
|
||||
view?.popView()
|
||||
}
|
||||
|
||||
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
|
||||
|
@ -0,0 +1,81 @@
|
||||
package io.github.wulkanowy.ui.modules.message.mailboxchooser
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.MailboxType
|
||||
import io.github.wulkanowy.databinding.ItemMailboxChooserBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class MailboxChooserAdapter @Inject constructor() :
|
||||
ListAdapter<MailboxChooserItem, MailboxChooserAdapter.ItemViewHolder>(Differ) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
ItemMailboxChooserBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
class ItemViewHolder(
|
||||
private val binding: ItemMailboxChooserBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: MailboxChooserItem) {
|
||||
with(binding) {
|
||||
mailboxItemName.text = item.mailbox?.getFirstLine()
|
||||
?: root.resources.getString(R.string.message_chip_all_mailboxes)
|
||||
mailboxItemSchool.text = item.mailbox?.getSecondLine()
|
||||
mailboxItemSchool.isVisible = !item.isAll
|
||||
|
||||
root.setOnClickListener { item.onClickListener(item.mailbox) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Mailbox.getFirstLine() = buildString {
|
||||
if (studentName.isNotBlank() && studentName != userName) {
|
||||
append(studentName)
|
||||
append(" - ")
|
||||
}
|
||||
append(userName)
|
||||
}
|
||||
|
||||
private fun Mailbox.getSecondLine() = buildString {
|
||||
append(schoolNameShort)
|
||||
append(" - ")
|
||||
append(getMailboxType(type))
|
||||
}
|
||||
|
||||
private fun getMailboxType(type: MailboxType): String = when (type) {
|
||||
MailboxType.STUDENT -> R.string.message_mailbox_type_student
|
||||
MailboxType.PARENT -> R.string.message_mailbox_type_parent
|
||||
MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian
|
||||
MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee
|
||||
MailboxType.UNKNOWN -> null
|
||||
}.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() }
|
||||
}
|
||||
|
||||
private object Differ : ItemCallback<MailboxChooserItem>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: MailboxChooserItem,
|
||||
newItem: MailboxChooserItem
|
||||
): Boolean {
|
||||
return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: MailboxChooserItem,
|
||||
newItem: MailboxChooserItem
|
||||
): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package io.github.wulkanowy.ui.modules.message.mailboxchooser
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.utils.parcelableArray
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MailboxChooserDialog : BaseDialogFragment<DialogMailboxChooserBinding>(), MailboxChooserView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MailboxChooserPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var mailboxAdapter: MailboxChooserAdapter
|
||||
|
||||
companion object {
|
||||
const val LISTENER_KEY = "mailbox_selected"
|
||||
const val MAILBOX_KEY = "selected_mailbox"
|
||||
const val REQUIRED_KEY = "is_mailbox_required"
|
||||
|
||||
fun newInstance(mailboxes: List<Mailbox>, isMailboxRequired: Boolean, folder: String) =
|
||||
MailboxChooserDialog().apply {
|
||||
arguments = bundleOf(
|
||||
MAILBOX_KEY to mailboxes.toTypedArray(),
|
||||
REQUIRED_KEY to isMailboxRequired,
|
||||
LISTENER_KEY to folder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
presenter.onAttachView(
|
||||
view = this,
|
||||
requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
|
||||
mailboxes = requireArguments().parcelableArray<Mailbox>(MAILBOX_KEY).orEmpty().toList(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountQuickDialogRecycler.adapter = mailboxAdapter
|
||||
}
|
||||
|
||||
override fun submitData(items: List<MailboxChooserItem>) {
|
||||
mailboxAdapter.submitList(items)
|
||||
}
|
||||
|
||||
override fun onMailboxSelected(item: Mailbox?) {
|
||||
setFragmentResult(
|
||||
requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(),
|
||||
result = bundleOf(MAILBOX_KEY to item),
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package io.github.wulkanowy.ui.modules.message.mailboxchooser
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
|
||||
data class MailboxChooserItem(
|
||||
val mailbox: Mailbox? = null,
|
||||
val isAll: Boolean = false,
|
||||
val onClickListener: (Mailbox?) -> Unit,
|
||||
)
|
@ -0,0 +1,38 @@
|
||||
package io.github.wulkanowy.ui.modules.message.mailboxchooser
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MailboxChooserPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<MailboxChooserView>(errorHandler, studentRepository) {
|
||||
|
||||
fun onAttachView(view: MailboxChooserView, mailboxes: List<Mailbox>, requireMailbox: Boolean) {
|
||||
super.onAttachView(view)
|
||||
|
||||
view.initView()
|
||||
Timber.i("Mailbox chooser view was initialized")
|
||||
view.submitData(getMailboxItems(mailboxes, requireMailbox))
|
||||
}
|
||||
|
||||
private fun getMailboxItems(
|
||||
mailboxes: List<Mailbox>,
|
||||
requireMailbox: Boolean,
|
||||
): List<MailboxChooserItem> = buildList {
|
||||
if (!requireMailbox) {
|
||||
add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect))
|
||||
}
|
||||
addAll(mailboxes.map {
|
||||
MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect)
|
||||
})
|
||||
}
|
||||
|
||||
fun onMailboxSelect(item: Mailbox?) {
|
||||
view?.onMailboxSelected(item)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package io.github.wulkanowy.ui.modules.message.mailboxchooser
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MailboxChooserView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun submitData(items: List<MailboxChooserItem>)
|
||||
|
||||
fun onMailboxSelected(item: Mailbox?)
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -74,15 +73,20 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindMessage(holder: MessageViewHolder, message: Message) {
|
||||
val context = holder.binding.root.context
|
||||
val recipientCount = (message.unreadBy ?: 0) + (message.readBy ?: 0)
|
||||
val isReceived = message.unreadBy == null
|
||||
|
||||
val readTextValue = when {
|
||||
!message.unread -> R.string.all_yes
|
||||
else -> R.string.all_no
|
||||
val readText = when {
|
||||
recipientCount > 1 -> {
|
||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||
}
|
||||
message.readBy == 1 || (isReceived && !message.unread) -> {
|
||||
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) {
|
||||
messagePreviewSubject.text = message.subject.ifBlank {
|
||||
|
@ -13,6 +13,7 @@ import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -23,6 +24,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.shareText
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -66,13 +68,12 @@ class MessagePreviewFragment :
|
||||
companion object {
|
||||
const val MESSAGE_ID_KEY = "message_id"
|
||||
|
||||
fun newInstance(message: Message): MessagePreviewFragment {
|
||||
return MessagePreviewFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) }
|
||||
}
|
||||
fun newInstance(message: Message) = MessagePreviewFragment().apply {
|
||||
arguments = bundleOf(MESSAGE_ID_KEY to message)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
@ -83,8 +84,8 @@ class MessagePreviewFragment :
|
||||
binding = FragmentMessagePreviewBinding.bind(view)
|
||||
messageContainer = binding.messagePreviewContainer
|
||||
presenter.onAttachView(
|
||||
this,
|
||||
(savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message
|
||||
view = this,
|
||||
message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
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.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
@ -21,7 +20,6 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val mailboxRepository: MailboxRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
||||
|
||||
@ -187,7 +185,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
messageRepository.deleteMessage(student, mailbox, message!!)
|
||||
}
|
||||
.onFailure {
|
||||
|
@ -19,11 +19,16 @@ import androidx.core.text.toHtml
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.databinding.ActivitySendMessageBinding
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
|
||||
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY
|
||||
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.hideSoftInput
|
||||
import io.github.wulkanowy.utils.nullableSerializable
|
||||
import io.github.wulkanowy.utils.showSoftInput
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -100,13 +105,17 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
||||
formSubjectValue = binding.sendMessageSubject.text.toString()
|
||||
formContentValue =
|
||||
binding.sendMessageMessageContent.text.toString().parseAsHtml().toString()
|
||||
binding.sendMessageFrom.setOnClickListener { presenter.onOpenMailboxChooser() }
|
||||
|
||||
presenter.onAttachView(
|
||||
view = this,
|
||||
reason = intent.getSerializableExtra(EXTRA_REASON) as? String,
|
||||
message = intent.getSerializableExtra(EXTRA_MESSAGE) as? Message,
|
||||
reply = intent.getSerializableExtra(EXTRA_REPLY) as? Boolean
|
||||
reason = intent.nullableSerializable(EXTRA_REASON),
|
||||
message = intent.nullableSerializable(EXTRA_MESSAGE),
|
||||
reply = intent.nullableSerializable(EXTRA_REPLY)
|
||||
)
|
||||
supportFragmentManager.setFragmentResultListener(LISTENER_KEY, this) { _, bundle ->
|
||||
presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@ -205,6 +214,14 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
|
||||
}
|
||||
}
|
||||
|
||||
override fun showMailboxChooser(mailboxes: List<Mailbox>) {
|
||||
MailboxChooserDialog.newInstance(
|
||||
mailboxes = mailboxes,
|
||||
isMailboxRequired = true,
|
||||
folder = LISTENER_KEY,
|
||||
).show(supportFragmentManager, "chooser")
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
finish()
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package io.github.wulkanowy.ui.modules.message.send
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.*
|
||||
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.Recipient
|
||||
import io.github.wulkanowy.data.logResourceStatus
|
||||
import io.github.wulkanowy.data.onResourceNotLoading
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.repositories.*
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.RecipientRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
@ -28,7 +28,6 @@ class SendMessagePresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val mailboxRepository: MailboxRepository,
|
||||
private val recipientRepository: RecipientRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
@ -36,10 +35,19 @@ class SendMessagePresenter @Inject constructor(
|
||||
|
||||
private val messageUpdateChannel = Channel<Unit>()
|
||||
|
||||
private var message: Message? = null
|
||||
private var isReplay: Boolean? = null
|
||||
|
||||
private var mailboxes: List<Mailbox> = emptyList()
|
||||
private var selectedMailbox: Mailbox? = null
|
||||
|
||||
fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
initializeSubjectStream()
|
||||
this.message = message
|
||||
this.isReplay = reply
|
||||
|
||||
Timber.i("Send message view was initialized")
|
||||
loadData(message, reply)
|
||||
with(view) {
|
||||
@ -47,7 +55,7 @@ class SendMessagePresenter @Inject constructor(
|
||||
view.showMessageBackupDialog()
|
||||
}
|
||||
reason?.let {
|
||||
setSubject("Usprawiedliwenie")
|
||||
setSubject("Usprawiedliwienie")
|
||||
setContent(it)
|
||||
}
|
||||
message?.let {
|
||||
@ -110,16 +118,31 @@ class SendMessagePresenter @Inject constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
fun onOpenMailboxChooser() {
|
||||
view?.showMailboxChooser(mailboxes)
|
||||
}
|
||||
|
||||
fun onMailboxSelected(mailbox: Mailbox?) {
|
||||
selectedMailbox = mailbox
|
||||
|
||||
loadData(message, isReplay)
|
||||
}
|
||||
|
||||
private fun loadData(message: Message?, reply: Boolean?) {
|
||||
resourceFlow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
|
||||
if (selectedMailbox == null && mailboxes.isEmpty()) {
|
||||
selectedMailbox = messageRepository.getMailboxByStudent(student)
|
||||
mailboxes = messageRepository.getMailboxes(student, false).toFirstResult()
|
||||
.dataOrNull.orEmpty()
|
||||
}
|
||||
|
||||
Timber.i("Loading recipients started")
|
||||
val recipients = createChips(
|
||||
recipients = recipientRepository.getRecipients(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
mailbox = selectedMailbox,
|
||||
type = MailboxType.EMPLOYEE,
|
||||
)
|
||||
)
|
||||
@ -130,7 +153,7 @@ class SendMessagePresenter @Inject constructor(
|
||||
message != null && reply == true -> recipientRepository.getMessageSender(
|
||||
student = student,
|
||||
message = message,
|
||||
mailbox = mailbox,
|
||||
mailbox = selectedMailbox,
|
||||
)
|
||||
else -> emptyList()
|
||||
}.let { createChips(it) }
|
||||
@ -139,39 +162,42 @@ class SendMessagePresenter @Inject constructor(
|
||||
messageRecipients.size
|
||||
)
|
||||
|
||||
Triple(mailbox, recipients, messageRecipients)
|
||||
recipients to messageRecipients
|
||||
}
|
||||
.logResourceStatus("load recipients")
|
||||
.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) ->
|
||||
view?.run {
|
||||
setMailbox(getMailboxName(mailbox))
|
||||
setRecipients(recipientChips)
|
||||
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
|
||||
selectedRecipientChips
|
||||
)
|
||||
showContent(true)
|
||||
}
|
||||
}
|
||||
is Resource.Error -> {
|
||||
view?.showContent(true)
|
||||
errorHandler.dispatch(it.error)
|
||||
.onResourceLoading {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
}
|
||||
.onResourceNotLoading {
|
||||
view?.run { showProgress(false) }
|
||||
}
|
||||
.onResourceError {
|
||||
view?.showContent(true)
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.onResourceSuccess {
|
||||
it.let { (recipientChips, selectedRecipientChips) ->
|
||||
view?.run {
|
||||
setMailbox(getMailboxName(selectedMailbox))
|
||||
setRecipients(recipientChips)
|
||||
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
|
||||
selectedRecipientChips
|
||||
)
|
||||
showContent(true)
|
||||
}
|
||||
}
|
||||
}.onResourceNotLoading {
|
||||
view?.run { showProgress(false) }
|
||||
}.launch()
|
||||
}
|
||||
.launch()
|
||||
}
|
||||
|
||||
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
|
||||
val mailbox = selectedMailbox ?: return
|
||||
|
||||
resourceFlow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val mailbox = mailboxRepository.getMailbox(student)
|
||||
messageRepository.sendMessage(
|
||||
student = student,
|
||||
subject = subject,
|
||||
@ -222,18 +248,21 @@ class SendMessagePresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMailboxName(mailbox: Mailbox): String {
|
||||
private fun getMailboxName(mailbox: Mailbox?): String {
|
||||
mailbox ?: return ""
|
||||
|
||||
// username - accountType [\n student name - ] (school short name)
|
||||
return buildString {
|
||||
append(mailbox.userName)
|
||||
append(" - ")
|
||||
append(getMailboxType(mailbox.type))
|
||||
appendLine()
|
||||
|
||||
if (mailbox.type == MailboxType.PARENT) {
|
||||
append(" - ")
|
||||
append(mailbox.studentName)
|
||||
append(" - ")
|
||||
}
|
||||
|
||||
append(" - ")
|
||||
append("(${mailbox.schoolNameShort})")
|
||||
}
|
||||
}
|
||||
@ -267,9 +296,9 @@ class SendMessagePresenter @Inject constructor(
|
||||
|
||||
private fun saveDraftMessage() {
|
||||
messageRepository.draftMessage = MessageDraft(
|
||||
view?.formRecipientsData!!,
|
||||
view?.formSubjectValue!!,
|
||||
view?.formContentValue!!
|
||||
recipients = view?.formRecipientsData!!,
|
||||
subject = view?.formSubjectValue!!,
|
||||
content = view?.formContentValue!!,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -61,4 +61,5 @@ interface SendMessageView : BaseView {
|
||||
fun getMessageBackupDialogStringWithRecipients(recipients: String): String
|
||||
|
||||
fun clearDraft()
|
||||
fun showMailboxChooser(mailboxes: List<Mailbox>)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user