1
0

Compare commits

...

88 Commits

Author SHA1 Message Date
5f1bb7c1d0 Merge branch 'release/0.22.2' into master 2020-10-30 12:31:50 +01:00
c6f4c868b2 Version 0.22.2 2020-10-30 12:31:41 +01:00
c634c64e70 Update hianalytics to 5.0.4.301 (#1003) 2020-10-30 12:24:00 +01:00
da2b7dbf7e Merge branch 'release/0.22.1' into develop 2020-10-30 01:49:56 +01:00
26267507eb Merge branch 'release/0.22.1' into master 2020-10-30 01:49:42 +01:00
24d0c5057b Version 0.22.1 2020-10-30 01:49:30 +01:00
b05026a6e6 New Crowdin updates (#1002) 2020-10-30 00:54:35 +01:00
8036f3d7f7 Add HMS flavor (#998) 2020-10-29 13:58:56 +01:00
23e309d38e New Crowdin updates (#993) 2020-10-26 11:54:27 +01:00
bf92c6b2e9 Fix grade header item object comparision (#999) 2020-10-25 12:16:19 +01:00
c00b5edaf7 Make AppGallery badge background transparent. (#997) 2020-10-22 16:17:52 +02:00
cb09ca13dc Bump junit from 4.13 to 4.13.1 (#995) 2020-10-15 18:36:31 +00:00
3d68b8e629 Bump gradle from 4.0.2 to 4.1.0 (#996) 2020-10-15 18:36:13 +00:00
b3173581e5 Merge tag '0.22.0' into develop
Version 0.22.0
2020-10-15 17:54:27 +02:00
ddac1d0f98 Merge branch 'release/0.22.0' into master 2020-10-15 17:54:22 +02:00
db6a359bea Version 0.22.0 2020-10-15 17:54:14 +02:00
e7221e6a32 Add Vulcan and Scrapper Exceptions to known exceptions (#994) 2020-10-15 15:55:59 +02:00
db9c2640c7 Add in-app updates support (#914) 2020-10-15 01:00:41 +02:00
ca67e144e4 Add ConnectException and StreamResetException to known exceptions (#992) 2020-10-13 22:32:15 +02:00
da2346ed83 New Crowdin updates (#987) 2020-10-11 21:00:48 +02:00
a87818f3d0 Cancel scheduled lesson notifications which turned into canceled (#991) 2020-10-11 20:51:03 +02:00
5092f8c0bf Reload grade list on mark all as read (#990) 2020-10-11 20:33:18 +02:00
af0787c0b1 Add support for BIG font (#989)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-10-10 02:53:25 +02:00
721b4ac797 Add support for night mode in account switcher dialog (#988) 2020-10-09 21:24:58 +02:00
26a69092cc Add AppGallery badge (#986) 2020-10-07 11:19:34 +02:00
2bd0c75055 Bump about_libraries from 8.3.1 to 8.4.2 (#985) 2020-10-06 21:56:46 +00:00
d6f3c57293 Bump moshi from 1.10.0 to 1.11.0 (#984) 2020-10-06 21:45:38 +00:00
518387e7bb Bump gradle from 4.0.1 to 4.0.2 (#982) 2020-10-06 21:43:56 +00:00
ca6dfbf2d0 Bump core-ktx from 1.3.1 to 1.3.2 (#983) 2020-10-06 21:43:50 +00:00
da6d8a74fd New Crowdin updates (#981) 2020-10-06 23:34:27 +02:00
e35e4ef152 New Crowdin updates (#948) 2020-10-03 13:01:11 +02:00
40fc6ec2e0 Upgrade android sdk to 30 (#966) 2020-10-03 01:08:57 +02:00
b91973aec3 Bump chucker from 3.2.0 to 3.3.0 (#979) 2020-10-01 15:38:29 +00:00
047e70ad46 Bump firebase-crashlytics from 17.2.1 to 17.2.2 (#974) 2020-09-30 20:13:18 +00:00
112c1eb793 Bump moshi from 1.9.3 to 1.10.0 (#972) 2020-09-30 20:13:01 +00:00
c479b31670 Bump coil from 1.0.0-rc2 to 1.0.0-rc3 (#976) 2020-09-30 20:01:45 +00:00
43ed8c8fce Bump firebase-messaging from 20.2.4 to 20.3.0 (#973) 2020-09-30 19:52:02 +00:00
613fa44c27 Bump about_libraries from 8.3.0 to 8.3.1 (#975) 2020-09-30 19:34:53 +00:00
f21216286d Bump mockk from 1.10.0 to 1.10.2 (#977) 2020-09-30 19:34:13 +00:00
7298d0d75a Bump google-services from 4.3.3 to 4.3.4 (#978) 2020-09-30 19:33:22 +00:00
ee0fbcdfd6 Merge tag '0.21.2' into develop
Version 0.21.2
2020-09-29 21:10:17 +02:00
c362ad12c7 Merge branch 'release/0.21.2' into master 2020-09-29 21:10:11 +02:00
8a1a712d6d Version 0.21.2 2020-09-29 21:10:05 +02:00
1f0f6b3e51 Fix string pair list type converter (#971) 2020-09-29 21:02:49 +02:00
11487e77ca Merge tag '0.21.1' into develop
Version 0.21.1
2020-09-29 11:43:58 +02:00
fd0fd4df55 Merge branch 'release/0.21.1' into master 2020-09-29 11:43:54 +02:00
d95a33787b Version 0.21.1 2020-09-29 11:43:49 +02:00
e5661098d9 Fix string pair list type converter (#970) 2020-09-29 11:42:54 +02:00
d020b01794 Merge tag '0.21.0' into develop
Version 0.21.0
2020-09-27 22:12:31 +02:00
d8b1264024 Merge branch 'release/0.21.0' into master 2020-09-27 22:12:27 +02:00
cddd17650b Version 0.21.0 2020-09-27 22:11:55 +02:00
a0f9c70036 Migrate from gson to moshi (#969) 2020-09-27 20:59:27 +02:00
2e05416fb5 Show groups next to subjects in timetable (#953) 2020-09-27 16:33:36 +02:00
d32ebd66de Add subjects sorting in grades (#946) 2020-09-27 16:28:39 +02:00
c6a99f1000 Add remembering the full screen mode in homework (#956)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-27 14:49:19 +02:00
bafe52e310 Different notification for notes and praises (#952) 2020-09-25 15:46:08 +02:00
e08abc1fc2 Show subjects without grades in "Grades" (#947) 2020-09-25 15:45:07 +02:00
2a74b11cce Add app shortcuts (#939)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-25 15:37:19 +02:00
b0b3ccfd53 Upgrade gradle wrapper to 6.6.1 (#968) 2020-09-20 21:54:59 +02:00
6c68456f7a Merge tag '0.20.5' into develop
Version 0.20.5
2020-09-19 13:03:32 +02:00
3e8e9b4ecc Merge branch 'release/0.20.5' into master 2020-09-19 13:03:28 +02:00
d6ebc343d5 Version 0.20.5 2020-09-19 01:25:34 +02:00
73be416807 Fix crash in flowWithResourceIn() (#967) 2020-09-19 00:57:55 +02:00
0cb65a29ba Merge tag '0.20.4' into develop
Version 0.20.4
2020-09-13 19:00:45 +02:00
13198f2ab4 Merge branch 'release/0.20.4' into master 2020-09-13 19:00:39 +02:00
cd92f37435 Version 0.20.4 2020-09-13 19:00:32 +02:00
5d8fb376ab Expand exam sync date range to next month (#960) 2020-09-13 18:37:34 +02:00
47150364d8 Fix lifecycle of timer tasks in timetable lessons (#958)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-13 16:27:53 +02:00
792b123598 Bump coil from 1.0.0-rc1 to 1.0.0-rc2 (#961) 2020-09-13 14:07:30 +00:00
acf5c8e9ba Bump firebase-crashlytics-gradle from 2.2.1 to 2.3.0 (#964) 2020-09-13 14:04:41 +00:00
53561668fc Bump hilt_version from 2.28.3-alpha to 2.29.1-alpha (#962) 2020-09-13 14:03:09 +00:00
7cfe58d311 Bump material from 1.2.0 to 1.2.1 (#963) 2020-09-13 14:02:31 +00:00
cd51fac621 Add eduportal.koszalin.pl register (#959) 2020-09-13 13:46:45 +02:00
adde5541e2 Move timetable notifications scheduling to background thread (#954) 2020-09-11 13:02:16 +02:00
6e56d3ff06 Ignore empty semesters on refresh (#955) 2020-09-09 13:28:44 +02:00
ec761f6329 Fix bug in grade statistics (#951) 2020-09-08 20:13:17 +02:00
6363c90e37 Disable sound of upcoming lessons notification (fix) (#950) 2020-09-07 20:28:32 +02:00
c30f105be5 Fix crash on unknown attendance category type (#949) 2020-09-07 09:35:26 +02:00
9f85b2206a Merge tag '0.20.3' into develop
Version 0.20.3
2020-09-04 23:42:12 +02:00
42515fd084 Merge branch 'release/0.20.3' into master 2020-09-04 23:42:07 +02:00
9a7c04fe7b Version 0.20.3 2020-09-04 23:42:01 +02:00
debb21f5f9 Add full stacktrace to errors list in sync now (#945) 2020-09-03 21:10:39 +02:00
18b9bf42e1 Fix crash in flowWithResourceIn() (#944) 2020-09-03 20:54:28 +02:00
6ded83d132 Fix attendance item description (#943) 2020-09-03 20:52:24 +02:00
71d37a1c6c Merge tag '0.20.2' into develop
Version 0.20.2
2020-09-02 00:23:49 +02:00
3975d06cde Merge branch 'release/0.20.2' into master 2020-09-02 00:23:45 +02:00
ee168bafe0 Version 0.20.2 2020-09-02 00:23:41 +02:00
42ed7e0ae1 Merge tag '0.20.1' into develop
Version 0.20.1
2020-09-02 00:14:35 +02:00
161 changed files with 1980 additions and 435 deletions

3
.gitignore vendored
View File

@ -113,3 +113,6 @@ Thumbs.db
!/gradle/wrapper/gradle-wrapper.jar
.idea/jarRepositories.xml
app/src/release/agconnect-services.json

View File

@ -3,8 +3,8 @@ jdk: oraclejdk8
env:
global:
- ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=29.0.3
- ANDROID_API_LEVEL=30
- ANDROID_BUILD_TOOLS_VERSION=30.0.2
cache:
directories:
@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- 0.20.1
- 0.22.2
android:
licenses:
@ -28,32 +28,37 @@ android:
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
# The SDK version used to compile your project
- android-$ANDROID_API_LEVEL
# Additional components
# Additional components
- extra-google-google_play_services
- extra-google-m2repository
- extra-android-m2repository
- addon-google_apis-google-$ANDROID_API_LEVEL
# Android emulator
# Android emulator
- android-22
- sys-img-armeabi-v7a-android-22
before_install:
- yes | sdkmanager "platforms;android-30"
- yes | sdkmanager "build-tools;30.0.2"
before_script:
# Launch emulator before the execution
- echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
- emulator -avd test -no-audio -no-window &
- android-wait-for-emulator
- adb shell input keyevent 82 &
- "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash"
# Launch emulator before the execution
- echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
- emulator -avd test -no-audio -no-window &
- android-wait-for-emulator
- adb shell input keyevent 82 &
- "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash"
script:
- ./gradlew dependencies --stacktrace --daemon
- fossa --no-ansi || true
- ./gradlew -Pcoverage testPlayDebugUnitTest --stacktrace --daemon
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace --daemon
- ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace --daemon
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
- |
if [ $TRAVIS_TAG ]; then
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
./gradlew publishPlayRelease -PenableFirebase --stacktrace;

View File

@ -32,14 +32,17 @@ Unofficial android VULCAN UONET+ register client for both students and their par
## Download
You can download the current beta version from the Google Play or the F-Droid store
You can download the current beta version from the Google Play, F-Droid or Huawei AppGallery store
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release

View File

@ -32,14 +32,17 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
## Pobierz
Aktualną wersję beta możesz pobrać ze sklepu Google Play lub F-Droid
Aktualną wersję beta możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Pobierz z Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
alt="Pobierz z Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Pobierz z F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Odkrywaj w AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania

View File

@ -10,16 +10,16 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
compileSdkVersion 30
buildToolsVersion '30.0.2'
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17
targetSdkVersion 29
versionCode 64
versionName "0.20.0"
targetSdkVersion 30
versionCode 75
versionName "0.22.2"
multiDexEnabled true
resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -69,12 +69,26 @@ android {
flavorDimensions "platform"
productFlavors {
hms {
dimension "platform"
minSdkVersion 19
manifestPlaceholders = [
install_channel: "AppGallery"
]
}
play {
dimension "platform"
manifestPlaceholders = [
install_channel: "Google Play"
]
}
fdroid {
dimension "platform"
manifestPlaceholders = [
install_channel: "F-Droid"
]
}
}
@ -112,13 +126,15 @@ play {
serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false
track = 'alpha'
updatePriority = 1
}
ext {
work_manager = "2.4.0"
room = "2.2.5"
chucker = "3.2.0"
mockk = "1.10.0"
chucker = "3.3.0"
mockk = "1.10.2"
moshi = "1.11.0"
}
configurations.all {
@ -126,14 +142,14 @@ configurations.all {
}
dependencies {
implementation "io.github.wulkanowy:sdk:0.20.1"
implementation "io.github.wulkanowy:sdk:0.22.2"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "androidx.core:core-ktx:1.3.1"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat-resources:1.2.0"
@ -147,7 +163,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.2.0"
implementation "com.google.android.material:material:1.2.1"
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -170,29 +186,35 @@ dependencies {
implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.2.2"
implementation "com.google.code.gson:gson:2.8.6"
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:1.0.0-rc1"
implementation "io.coil-kt:coil:1.0.0-rc3"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation 'com.google.firebase:firebase-analytics:17.5.0'
playImplementation 'com.google.firebase:firebase-analytics:17.6.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1"
playImplementation 'com.google.firebase:firebase-messaging:20.2.4'
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.1'
playImplementation 'com.google.firebase:firebase-messaging:20.3.0'
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
hmsImplementation 'com.huawei.hms:hianalytics:5.0.4.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.13"
testImplementation "junit:junit:4.13.1"
testImplementation "io.mockk:mockk:$mockk"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9'
@ -205,3 +227,4 @@ dependencies {
}
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect'

View File

@ -35,13 +35,13 @@ task jacocoTestReport(type: JacocoReport) {
dir: "$buildDir/intermediates/classes/debug",
excludes: excludes
) + fileTree(
dir: "$buildDir/tmp/kotlin-classes/playDebug",
dir: "$buildDir/tmp/kotlin-classes/fdroidDebug",
excludes: excludes
))
sourceDirectories.setFrom(files([
"src/main/java",
"src/play/java"
"src/fdroid/java"
]))
executionData.setFrom(fileTree(
dir: project.projectDir,

View File

@ -0,0 +1,33 @@
{
"agcgw":{
"backurl":"connect-dre.dbankcloud.cn",
"url":"connect-dre.hispace.hicloud.com"
},
"client":{
"cp_id":"890048000024105546",
"product_id":"",
"client_id":"",
"client_secret":"",
"app_id":"101440411",
"package_name":"io.github.wulkanowy.dev",
"api_key":""
},
"service":{
"analytics":{
"collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"resource_id":"p1",
"channel_id":""
},
"search":{
"url":"https://search-dre.cloud.huawei.com"
},
"cloudstorage":{
"storage_url":"https://ops-dre.agcstorage.link"
},
"ml":{
"mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
}
},
"region":"DE",
"configuration_version":"1.0"
}

View File

@ -6,7 +6,7 @@ import javax.inject.Singleton
@Singleton
@Suppress("UNUSED_PARAMETER")
class FirebaseAnalyticsHelper @Inject constructor() {
class AnalyticsHelper @Inject constructor() {
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
// do nothing

View File

@ -8,6 +8,6 @@ open class TimberTreeNoOp : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {}
}
class CrashlyticsTree : TimberTreeNoOp()
class CrashLogTree : TimberTreeNoOp()
class CrashlyticsExceptionTree : TimberTreeNoOp()
class CrashLogExceptionTree : TimberTreeNoOp()

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.view.View
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER")
class UpdateHelper @Inject constructor() {
lateinit var messageContainer: View
fun checkAndInstallUpdates(activity: Activity) {}
fun onActivityResult(requestCode: Int, resultCode: Int) {}
fun onResume(activity: Activity) {}
}

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
private val analytics by lazy { HiAnalytics.getInstance(context) }
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)
}
}
analytics.onEvent(name, this)
}
}
fun setCurrentScreen(activity: Activity, name: String?) {
analytics.onEvent("screen_view", Bundle().apply {
putString("screen_name", name)
putString("screen_class", activity::class.simpleName)
})
}
}

View File

@ -0,0 +1,52 @@
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
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import java.io.InterruptedIOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
private val connectCrash by lazy { AGConnectCrash.getInstance() }
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
connectCrash.log(format(priority, tag, message))
}
}
class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) {
private val connectCrash by lazy { AGConnectCrash.getInstance() }
override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?): Boolean {
return when (t) {
is FeatureDisabledException,
is FeatureNotAvailableException,
is UnknownHostException,
is SocketTimeoutException,
is InterruptedIOException -> true
else -> super.skipLog(priority, tag, message, t)
}
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
connectCrash.setCustomKey("priority", priority)
connectCrash.setCustomKey("tag", tag.orEmpty())
connectCrash.setCustomKey("message", message)
connectCrash.log(priority, t?.stackTraceToString())
if (t != null) {
connectCrash.log(priority, t.stackTraceToString())
} else {
connectCrash.log(priority, StackTraceRecorder(format(priority, tag, message)).stackTraceToString())
}
}
}

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.view.View
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER")
class UpdateHelper @Inject constructor() {
lateinit var messageContainer: View
fun checkAndInstallUpdates(activity: Activity) {}
fun onActivityResult(requestCode: Int, resultCode: Int) {}
fun onResume(activity: Activity) {}
}

View File

@ -110,6 +110,11 @@
android:resource="@xml/provider_paths" />
</provider>
<meta-data
android:name="install_channel"
android:value="${install_channel}">
</meta-data>
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
<provider

View File

@ -14,8 +14,8 @@ import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashlyticsExceptionTree
import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.DebugLogTree
import timber.log.Timber
import javax.inject.Inject
@ -56,8 +56,8 @@ class WulkanowyApp : Application(), Configuration.Provider {
.build()
)
} else {
Timber.plant(CrashlyticsExceptionTree())
Timber.plant(CrashlyticsTree())
Timber.plant(CrashLogExceptionTree())
Timber.plant(CrashLogTree())
}
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
}

View File

@ -33,7 +33,11 @@ internal class RepositoryModule {
setSimpleHttpLogger { Timber.d(it) }
// for debug only
addInterceptor(ChuckerInterceptor(context, chuckerCollector), true)
addInterceptor(ChuckerInterceptor(
context = context,
collector = chuckerCollector,
alwaysReadResponseBody = true
), true)
}
}

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.data
data class Resource<out T>(val status: Status, val data: T?, val error: Throwable?) {
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)

View File

@ -1,8 +1,9 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
@ -12,6 +13,16 @@ import java.util.Date
class Converters {
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
private val integerListAdapter by lazy {
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
}
private val stringListPairAdapter by lazy {
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
}
@TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run {
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate()
@ -39,22 +50,22 @@ class Converters {
fun intToMonth(value: Int?) = value?.let { Month.of(it) }
@TypeConverter
fun intListToGson(list: List<Int>): String {
return Gson().toJson(list)
fun intListToJson(list: List<Int>): String {
return integerListAdapter.toJson(list)
}
@TypeConverter
fun gsonToIntList(value: String): List<Int> {
return Gson().fromJson(value, object : TypeToken<List<Int>>() {}.type)
fun jsonToIntList(value: String): List<Int> {
return integerListAdapter.fromJson(value).orEmpty()
}
@TypeConverter
fun stringPairListToGson(list: List<Pair<String, String>>): String {
return Gson().toJson(list)
fun stringPairListToJson(list: List<Pair<String, String>>): String {
return stringListPairAdapter.toJson(list)
}
@TypeConverter
fun gsonToStringPairList(value: String): List<Pair<String, String>> {
return Gson().fromJson(value, object : TypeToken<List<Pair<String, String>>>() {}.type)
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
return stringListPairAdapter.fromJson(value).orEmpty()
}
}

View File

@ -0,0 +1,68 @@
package io.github.wulkanowy.data.db.adapters
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
object PairAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (type !is ParameterizedType || List::class.java != type.rawType) return null
if (type.actualTypeArguments[0] != Pair::class.java) return null
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
return PairAdapter(listAdapter, mapAdapter)
}
private class PairAdapter(
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
private val mapAdapter: JsonAdapter<Map<String, String>>,
) : JsonAdapter<List<Pair<String, String>>>() {
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
writer.beginArray()
value?.forEach {
writer.beginObject()
writer.name("first").value(it.first)
writer.name("second").value(it.second)
writer.endObject()
}
writer.endArray()
}
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
else deserializeGsonPair(reader)
}
// for compatibility with 0.21.0
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
val map = mapAdapter.fromJson(reader) ?: return null
return map.entries.map {
it.key to it.value
}
}
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
val list = listAdapter.fromJson(reader) ?: return null
return list.map {
require(it.size == 2) {
"pair with more or less than two elements: $list"
}
it["first"].orEmpty() to it["second"].orEmpty()
}
}
}
}

View File

@ -12,7 +12,7 @@ interface MessagesDao : BaseDao<Message> {
@Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment>
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>

View File

@ -36,12 +36,6 @@ data class Message(
var unread: Boolean,
@ColumnInfo(name = "unread_by")
val unreadBy: Int,
@ColumnInfo(name = "read_by")
val readBy: Int,
val removed: Boolean,
@ColumnInfo(name = "has_attachments")
@ -54,5 +48,11 @@ data class Message(
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = ""
}

View File

@ -1,3 +1,9 @@
package io.github.wulkanowy.data.pojos
class Contributor(val displayName: String, val githubUsername: String)
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Contributor(
val displayName: String,
val githubUsername: String
)

View File

@ -1,7 +1,8 @@
package io.github.wulkanowy.data.repositories.appcreator
import android.content.res.AssetManager
import com.google.gson.Gson
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.utils.DispatchersProvider
import kotlinx.coroutines.withContext
@ -15,9 +16,9 @@ class AppCreatorRepository @Inject constructor(
) {
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
Gson().fromJson(
assets.open("contributors.json").bufferedReader().use { it.readText() },
Array<Contributor>::class.java
).toList()
val moshi = Moshi.Builder().build()
val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
val adapter = moshi.adapter<List<Contributor>>(type)
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
}
}

View File

@ -2,9 +2,9 @@ package io.github.wulkanowy.data.repositories.exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import java.time.LocalDate
import javax.inject.Inject
@ -18,8 +18,8 @@ class ExamRepository @Inject constructor(
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getExams(semester, start.monday, end.sunday) },
fetch = { remote.getExams(student, semester, start.monday, end.sunday) },
query = { local.getExams(semester, start.startExamsDay, start.endExamsDay) },
fetch = { remote.getExams(student, semester, start.startExamsDay, start.endExamsDay) },
saveFetchResult = { old, new ->
local.deleteExams(old uniqueSubtract new)
local.saveExams(new uniqueSubtract old)

View File

@ -6,7 +6,6 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
@ -29,7 +28,7 @@ class MessageLocal @Inject constructor(
messagesDb.deleteAll(messages)
}
fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment> {
fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment?> {
return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId)
}

View File

@ -30,12 +30,12 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
date = it.date ?: now(),
folderId = it.folderId,
unread = it.unread ?: false,
unreadBy = it.unreadBy ?: 0,
readBy = it.readBy ?: 0,
removed = it.removed,
hasAttachments = it.hasAttachments
).apply {
content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
}
}
}

View File

@ -34,12 +34,14 @@ class MessageRepository @Inject constructor(
fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource(
shouldFetch = {
checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isEmpty()
},
query = { local.getMessageWithAttachment(student, message) },
fetch = { remote.getMessagesContentDetails(student, it.message, markAsRead) },
fetch = { remote.getMessagesContentDetails(student, it!!.message, markAsRead) },
saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" })
local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply {
id = old.message.id
content = content.ifBlank { downloadedMessage }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import javax.inject.Inject
import javax.inject.Singleton
@ -74,12 +75,25 @@ class PreferencesRepository @Inject constructor(
val fillMessageContent: Boolean
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
val showGroupsInPlan: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_groups, R.bool.pref_default_timetable_show_groups)
val showWholeClassPlan: String
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue(getString(R.string.pref_key_grade_sorting_mode, R.string.pref_default_grade_sorting_mode))
val showTimetableTimers: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
var isHomeworkFullscreen: Boolean
get() = getBoolean(R.string.pref_key_homework_fullscreen, R.bool.pref_default_homework_fullscreen)
set(value) = sharedPref.edit().putBoolean("homework_fullscreen", value).apply()
val showSubjectsWithoutGrades: Boolean
get() = getBoolean(R.string.pref_key_subjects_without_grades, R.bool.pref_default_subjects_without_grades)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -41,7 +42,7 @@ class SemesterRepository @Inject constructor(
private suspend fun refreshSemesters(student: Student) {
val new = remote.getSemesters(student)
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!")
if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = local.getSemesters(student)
local.deleteSemesters(old.uniqueSubtract(new))

View File

@ -26,7 +26,9 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.LocalDateTime
import java.time.LocalDateTime.now
@ -35,7 +37,8 @@ import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val alarmManager: AlarmManager,
private val preferencesRepository: PreferencesRepository
private val preferencesRepository: PreferencesRepository,
private val dispatchersProvider: DispatchersProvider,
) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
@ -44,13 +47,15 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
}
fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
}
}
}
@ -61,28 +66,34 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
lessons.groupBy { it.date }
.map { it.value.sortedBy { lesson -> lesson.start } }
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } }
.map { day ->
day.forEachIndexed { index, lesson ->
val intent = createIntent(student, lesson, day.getOrNull(index + 1))
withContext(dispatchersProvider.backgroundThread) {
lessons.groupBy { it.date }
.map { it.value.sortedBy { lesson -> lesson.start } }
.map { it.filter { lesson -> lesson.isStudentPlan } }
.map { day ->
val canceled = day.filter { it.canceled }
val active = day.filter { !it.canceled }
if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson))
}
cancelScheduled(canceled)
active.forEachIndexed { index, lesson ->
val intent = createIntent(student, lesson, active.getOrNull(index + 1))
if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
if (day.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson))
}
if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
if (active.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
}
}
}
}
}
}
}
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {

View File

@ -44,6 +44,7 @@ class SyncManager @Inject constructor(
if (SDK_INT >= O) {
channels.forEach { it.create() }
notificationManager.deleteNotificationChannel("lesson_channel")
notificationManager.deleteNotificationChannel("new_entries_channel")
}

View File

@ -50,13 +50,16 @@ class SyncWorker @WorkerInject constructor(
} catch (e: Throwable) {
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
else e
else {
Timber.e(e)
e
}
}
}
val result = when {
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(Data.Builder()
.putString("error", exceptions.toString())
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
.build()
)
}

View File

@ -17,7 +17,7 @@ class UpcomingLessonsChannel @Inject constructor(
) : Channel {
companion object {
const val CHANNEL_ID = "lesson_channel"
const val CHANNEL_ID = "upcoming_lesson_channel"
}
override fun create() {

View File

@ -3,8 +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.repositories.exam.ExamRepository
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult
import java.time.LocalDate.now
import javax.inject.Inject
@ -12,6 +10,6 @@ import javax.inject.Inject
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
examRepository.getExams(student, semester, now().monday, now().sunday, true).waitForResult()
examRepository.getExams(student, semester, now(), now(), true).waitForResult()
}
}

View File

@ -14,6 +14,9 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.NEUTRAL
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.POSITIVE
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
@ -41,8 +44,20 @@ class NoteWork @Inject constructor(
private fun notify(notes: List<Note>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size))
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size))
.setContentTitle(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)
}
)
.setContentText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_notify_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_notify_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)
}
)
.setSmallIcon(R.drawable.ic_stat_note)
.setAutoCancel(true)
.setDefaults(DEFAULT_ALL)
@ -52,7 +67,13 @@ class NoteWork @Inject constructor(
PendingIntent.getActivity(context, MainView.Section.NOTE.id,
MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size))
setSummaryText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_number_item, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_number_item, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)
}
)
notes.forEach { addLine("${it.teacher}: ${it.category}") }
this
})

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@ -63,7 +64,7 @@ open class BasePresenter<T : BaseView>(
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
jobs[individualJobTag]?.cancel()
val job = launchIn(this@BasePresenter)
val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
jobs[individualJobTag] = job
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
return job

View File

@ -22,9 +22,11 @@ import io.github.wulkanowy.utils.getString
import io.github.wulkanowy.utils.openAppInMarket
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import okhttp3.internal.http2.StreamResetException
import java.io.InterruptedIOException
import java.io.PrintWriter
import java.io.StringWriter
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject
@ -85,6 +87,8 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
errorDialogReport.isEnabled = when (error) {
is UnknownHostException,
is InterruptedIOException,
is ConnectException,
is StreamResetException,
is SocketTimeoutException,
is ServiceUnavailableException,
is FeatureDisabledException,

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import timber.log.Timber
import javax.inject.Inject
@ -12,7 +12,7 @@ class AboutPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val appInfo: AppInfo,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<AboutView>(errorHandler, studentRepository) {
override fun onAttachView(view: AboutView) {

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.description
import javax.inject.Inject
class AttendanceAdapter @Inject constructor() :
@ -34,7 +35,7 @@ class AttendanceAdapter @Inject constructor() :
with(holder.binding) {
attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject
attendanceItemDescription.text = item.name
attendanceItemDescription.setText(item.description)
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.description
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString
@ -43,7 +44,7 @@ class AttendanceDialog : DialogFragment() {
with(binding) {
attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name
attendanceDialogDescription.setText(attendance.description)
attendanceDialogDate.text = attendance.date.toFormattedString()
attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }

View File

@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
@ -34,7 +34,7 @@ class AttendancePresenter @Inject constructor(
private val attendanceRepository: AttendanceRepository,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<AttendanceView>(errorHandler, studentRepository) {
private var baseDate: LocalDate = now().previousOrSameSchoolDay

View File

@ -8,7 +8,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -22,7 +22,7 @@ class AttendanceSummaryPresenter @Inject constructor(
private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val subjectRepository: SubjectRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<AttendanceSummaryView>(errorHandler, studentRepository) {
private var subjects = emptyList<Subject>()

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
@ -30,7 +30,7 @@ class ExamPresenter @Inject constructor(
studentRepository: StudentRepository,
private val examRepository: ExamRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<ExamView>(errorHandler, studentRepository) {
private var baseDate: LocalDate = now().nextOrSameSchoolDay

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCurrentOrLast
import kotlinx.coroutines.delay
@ -18,7 +18,7 @@ class GradePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
var selectedIndex = 0

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.ui.modules.grade
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date");
companion object {
fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ALPHABETIC
}
}

View File

@ -40,10 +40,6 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
}
fun updateDetailsItem(position: Int, grade: Grade) {
if (items.getOrNull(position)?.viewType != ViewType.ITEM) {
Timber.e("Trying to update item $position on list ${items.size} size, expanded position: $expandedPosition")
return
}
items[position] = GradeDetailsItem(grade, ViewType.ITEM)
notifyItemChanged(position)
}
@ -62,10 +58,6 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
val headerPosition = headers.indexOf(item)
val itemPosition = items.indexOf(item)
if (headerPosition == NO_POSITION || itemPosition == NO_POSITION) {
Timber.e("Invalid update header positions! Header: $headerPosition, item: $itemPosition")
}
headers[headerPosition] = item
items[itemPosition] = item
notifyItemChanged(itemPosition)

View File

@ -14,6 +14,7 @@ data class GradeDetailsHeader(
val subject: String,
val average: Double?,
val pointsSum: String?,
var newGrades: Int,
val grades: List<GradeDetailsItem>
)
) {
var newGrades = 0
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -10,7 +11,9 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
@ -26,7 +29,7 @@ class GradeDetailsPresenter @Inject constructor(
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository,
private val averageProvider: GradeAverageProvider,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<GradeDetailsView>(errorHandler, studentRepository) {
private var newGradesAmount: Int = 0
@ -78,7 +81,10 @@ class GradeDetailsPresenter @Inject constructor(
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Select mark grades as read")
Status.SUCCESS -> Timber.i("Mark as read result: Success")
Status.SUCCESS -> {
Timber.i("Mark as read result: Success")
loadData(currentSemesterId, false)
}
Status.ERROR -> {
Timber.i("Mark as read result: An exception occurred")
errorHandler.dispatch(it.error!!)
@ -184,10 +190,20 @@ class GradeDetailsPresenter @Inject constructor(
}
}
@SuppressLint("DefaultLocale")
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
return items
.filter { it.grades.isNotEmpty() }
.sortedBy { it.subject }
.let { gradesWithAverages ->
if (!preferencesRepository.showSubjectsWithoutGrades) {
gradesWithAverages.filter { it.grades.isNotEmpty() }
} else gradesWithAverages
}
.let {
when (preferencesRepository.gradeSortingMode) {
DATE -> it.sortedByDescending { gradeDetailsWithAverage -> gradeDetailsWithAverage.grades.firstOrNull()?.date }
ALPHABETIC -> it.sortedBy { gradeDetailsWithAverage -> gradeDetailsWithAverage.subject.toLowerCase() }
}
}
.map { (subject, average, points, _, grades) ->
val subItems = grades
.sortedByDescending { it.date }
@ -197,9 +213,10 @@ class GradeDetailsPresenter @Inject constructor(
subject = subject,
average = average,
pointsSum = points,
newGrades = grades.filter { grade -> !grade.isRead }.size,
grades = subItems
), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
).apply {
newGrades = grades.filter { grade -> !grade.isRead }.size
}, ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
}.flatten()
}

View File

@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -23,7 +23,7 @@ class GradeStatisticsPresenter @Inject constructor(
private val subjectRepository: SubjectRepository,
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<GradeStatisticsView>(errorHandler, studentRepository) {
private var subjects = emptyList<Subject>()
@ -164,8 +164,8 @@ class GradeStatisticsPresenter @Inject constructor(
Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success")
view?.run {
showEmpty(it.data!!.isEmpty())
showContent(it.data.isNotEmpty())
showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty())
showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty())
showErrorView(false)
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -18,7 +18,7 @@ class GradeSummaryPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val averageProvider: GradeAverageProvider,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
@ -29,7 +29,7 @@ class HomeworkPresenter @Inject constructor(
studentRepository: StudentRepository,
private val homeworkRepository: HomeworkRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<HomeworkView>(errorHandler, studentRepository) {
private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay

View File

@ -29,6 +29,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
attachments = value?.attachments.orEmpty()
}
var isHomeworkFullscreen = false
var onAttachmentClickListener: (url: String) -> Unit = {}
var onFullScreenClickListener = {}
@ -67,6 +69,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
homeworkDialogSubject.text = homework?.subject
homeworkDialogTeacher.text = homework?.teacher
homeworkDialogContent.text = homework?.content
homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE
homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE
homeworkDialogFullScreen.setOnClickListener {
homeworkDialogFullScreen.visibility = GONE
homeworkDialogFullScreenExit.visibility = VISIBLE

View File

@ -62,12 +62,25 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
homeworkDialogClose.setOnClickListener { dismiss() }
}
if (presenter.isHomeworkFullscreen) {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
} else {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
}
with(binding.homeworkDialogRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = detailsAdapter.apply {
onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) }
onFullScreenClickListener = { dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) }
onFullScreenExitClickListener = { dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) }
onFullScreenClickListener = {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
presenter.isHomeworkFullscreen = true
}
onFullScreenExitClickListener = {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
presenter.isHomeworkFullscreen = false
}
isHomeworkFullscreen = presenter.isHomeworkFullscreen
homework = this@HomeworkDetailsDialog.homework
}
}

View File

@ -3,10 +3,11 @@ package io.github.wulkanowy.ui.modules.homework.details
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.repositories.homework.HomeworkRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@ -16,9 +17,16 @@ class HomeworkDetailsPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val homeworkRepository: HomeworkRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) {
var isHomeworkFullscreen
get() = preferencesRepository.isHomeworkFullscreen
set(value) {
preferencesRepository.isHomeworkFullscreen = value
}
override fun onAttachView(view: HomeworkDetailsView) {
super.onAttachView(view)
view.initView()

View File

@ -14,6 +14,7 @@ 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.utils.UpdateHelper
import io.github.wulkanowy.utils.setOnSelectPageListener
import javax.inject.Inject
@ -25,6 +26,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
@Inject
lateinit var updateHelper: UpdateHelper
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
@ -37,8 +41,20 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.loginToolbar)
messageContainer = binding.loginContainer
updateHelper.messageContainer = binding.loginContainer
presenter.onAttachView(this)
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
}
override fun initView() {

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank
@ -17,7 +17,7 @@ import javax.inject.Inject
class LoginAdvancedPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<LoginAdvancedView>(loginErrorHandler, studentRepository) {
override fun onAttachView(view: LoginAdvancedView) {

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank
@ -15,7 +15,7 @@ import javax.inject.Inject
class LoginFormPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.recover.RecoverRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank
@ -15,7 +15,7 @@ import javax.inject.Inject
class LoginRecoverPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: RecoverErrorHandler,
private val analytics: FirebaseAnalyticsHelper,
private val analytics: AnalyticsHelper,
private val recoverRepository: RecoverRepository
) : BasePresenter<LoginRecoverView>(loginErrorHandler, studentRepository) {

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach
@ -17,7 +17,7 @@ import javax.inject.Inject
class LoginStudentSelectPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<LoginStudentSelectView>(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank
@ -16,7 +16,7 @@ import javax.inject.Inject
class LoginSymbolPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<LoginSymbolView>(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null

View File

@ -5,7 +5,7 @@ import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -16,7 +16,7 @@ class LuckyNumberPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val luckyNumberRepository: LuckyNumberRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<LuckyNumberView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable

View File

@ -1,14 +1,21 @@
package io.github.wulkanowy.ui.modules.main
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
@ -31,7 +38,9 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.safelyPopFragments
@ -46,7 +55,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
override lateinit var presenter: MainPresenter
@Inject
lateinit var analytics: FirebaseAnalyticsHelper
lateinit var analytics: AnalyticsHelper
@Inject
lateinit var updateHelper: UpdateHelper
@Inject
lateinit var appInfo: AppInfo
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
@ -59,7 +74,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
return Intent(context, MainActivity::class.java)
.apply {
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
startMenu?.let { putExtra(EXTRA_START_MENU, it) }
startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
}
}
}
@ -83,18 +98,58 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance()
)
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.mainToolbar)
messageContainer = binding.mainFragmentContainer
updateHelper.messageContainer = binding.mainFragmentContainer
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section)
presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
with(navController) {
initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments[startMenuMoreIndex])
}
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
@SuppressLint("NewApi")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun initShortcuts() {
val shortcutsList = mutableListOf<ShortcutInfo>()
listOf(
Triple(getString(R.string.grade_title), R.drawable.ic_shortcut_grade, MainView.Section.GRADE),
Triple(getString(R.string.attendance_title), R.drawable.ic_shortcut_attendance, MainView.Section.ATTENDANCE),
Triple(getString(R.string.exam_title), R.drawable.ic_shortcut_exam, MainView.Section.EXAM),
Triple(getString(R.string.timetable_title), R.drawable.ic_shortcut_timetable, MainView.Section.TIMETABLE),
Triple(getString(R.string.message_title), R.drawable.ic_shortcut_message, MainView.Section.MESSAGE)
).forEach { (title, icon, enum) ->
shortcutsList.add(ShortcutInfo.Builder(applicationContext, title)
.setShortLabel(title)
.setLongLabel(title)
.setIcon(Icon.createWithResource(applicationContext, icon))
.setIntents(arrayOf(
Intent(applicationContext, MainActivity::class.java).setAction(Intent.ACTION_VIEW),
Intent(applicationContext, MainActivity::class.java).putExtra(EXTRA_START_MENU, enum.id)
.setAction(Intent.ACTION_VIEW).addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)))
.build())
}
getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@ -143,8 +198,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
analytics.setCurrentScreen(this, name)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
else false
}

View File

@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import timber.log.Timber
import javax.inject.Inject
@ -17,7 +17,7 @@ class MainPresenter @Inject constructor(
studentRepository: StudentRepository,
private val prefRepository: PreferencesRepository,
private val syncManager: SyncManager,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<MainView>(errorHandler, studentRepository) {
fun onAttachView(view: MainView, initMenu: MainView.Section?) {

View File

@ -174,7 +174,7 @@ class MessagePreviewFragment :
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun printDocument(html: String, jobName: String) {
val webView = WebView(activity)
val webView = WebView(requireContext())
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false

View File

@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
@ -24,7 +24,7 @@ class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
private val analytics: FirebaseAnalyticsHelper,
private val analytics: AnalyticsHelper,
private var appInfo: AppInfo
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
@ -64,7 +64,8 @@ class MessagePreviewPresenter @Inject constructor(
when (it.status) {
Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started")
Status.SUCCESS -> {
Timber.i("Loading message ${it.data!!.message.messageId} preview result: Success ")
Timber.i("Loading message ${message.messageId} preview result: Success ")
checkNotNull(it.data, { "Can't find message in local db! Probably no longer exist in this folder" })
this@MessagePreviewPresenter.message = it.data.message
this@MessagePreviewPresenter.attachments = it.data.attachments
view?.apply {
@ -194,6 +195,7 @@ class MessagePreviewPresenter @Inject constructor(
view?.run {
lastError = error
setErrorDetails(message)
showContent(false)
showErrorView(true)
setErrorRetryCallback { retryCallback() }
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.message.send
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Rect
@ -74,6 +75,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean)
}
@SuppressLint("ClickableViewAccessibility")
override fun initView() {
setUpExtendedHitArea()
with(binding) {
@ -87,8 +89,8 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.sendMessageMenuSend) presenter.onSend()
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.sendMessageMenuSend) presenter.onSend()
else false
}

View File

@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.toFormattedString
@ -27,7 +27,7 @@ class SendMessagePresenter @Inject constructor(
private val reportingUnitRepository: ReportingUnitRepository,
private val recipientRepository: RecipientRepository,
private val preferencesRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<SendMessageView>(errorHandler, studentRepository) {
fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) {

View File

@ -8,7 +8,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.toFormattedString
@ -32,7 +32,7 @@ class MessageTabPresenter @Inject constructor(
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
lateinit var folder: MessageFolder

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
@ -21,7 +21,7 @@ class MobileDevicePresenter @Inject constructor(
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val mobileDeviceRepository: MobileDeviceRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<MobileDeviceView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
@ -18,7 +18,7 @@ class MobileDeviceTokenPresenter @Inject constructor(
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val mobileDeviceRepository: MobileDeviceRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<MobileDeviceTokenVIew>(errorHandler, studentRepository) {
override fun onAttachView(view: MobileDeviceTokenVIew) {

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
@ -21,7 +21,7 @@ class NotePresenter @Inject constructor(
studentRepository: StudentRepository,
private val noteRepository: NoteRepository,
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<NoteView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -18,7 +18,7 @@ class SchoolPresenter @Inject constructor(
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schoolRepository: SchoolRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<SchoolView>(errorHandler, studentRepository) {
private var address: String? = null

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.teacher.TeacherRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
@ -18,7 +18,7 @@ class TeacherPresenter @Inject constructor(
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val teacherRepository: TeacherRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<TeacherView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable

View File

@ -9,7 +9,7 @@ import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.isHolidays
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.onEach
@ -22,7 +22,7 @@ class SettingsPresenter @Inject constructor(
studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository,
private val timetableNotificationHelper: TimetableNotificationSchedulerHelper,
private val analytics: FirebaseAnalyticsHelper,
private val analytics: AnalyticsHelper,
private val syncManager: SyncManager,
private val chuckerCollector: ChuckerCollector,
private val appInfo: AppInfo

View File

@ -6,6 +6,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
@ -40,12 +41,14 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var showWholeClassPlan: String = "no"
var showGroupsInPlan: Boolean = false
var showTimers: Boolean = false
private val timers = mutableMapOf<Int, Timer>()
private fun resetTimers() {
Timber.d("Timetable timers reset")
fun resetTimers() {
Timber.d("Timetable timers (${timers.size}) reset")
with(timers) {
forEach { (_, timer) -> timer.cancel() }
clear()
@ -69,11 +72,6 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
resetTimers()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val lesson = items[position]
@ -103,6 +101,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
with(binding) {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
timetableItemGroup.text = lesson.group
timetableItemRoom.text = lesson.room
timetableItemTeacher.text = lesson.teacher
timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
@ -112,8 +111,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
bindNormalDescription(binding, lesson)
bindNormalColors(binding, lesson)
if (lesson.isStudentPlan && showTimers) timers[position] = timer(period = 1000) {
root.post { updateTimeLeft(binding, lesson, position) }
if (lesson.isStudentPlan && showTimers) {
timers[position] = timer(period = 1000) {
if (ViewCompat.isAttachedToWindow(root)) {
root.post { updateTimeLeft(binding, lesson, position) }
}
}
} else {
// reset item on set changed
timetableItemTimeUntil.visibility = GONE
@ -218,6 +221,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableItemDescription.text = lesson.info
timetableItemRoom.visibility = GONE
timetableItemGroup.visibility = GONE
timetableItemTeacher.visibility = GONE
timetableItemDescription.setTextColor(root.context.getThemeAttrColor(
@ -227,6 +231,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} else {
timetableItemDescription.visibility = GONE
timetableItemRoom.visibility = VISIBLE
timetableItemGroup.visibility = if (showGroupsInPlan && lesson.group.isNotBlank()) VISIBLE else GONE
timetableItemTeacher.visibility = VISIBLE
}
}

View File

@ -88,11 +88,12 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
else false
}
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean) {
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) {
with(timetableAdapter) {
items = data.toMutableList()
showTimers = showTimetableTimers
showWholeClassPlan = showWholeClassPlanType
showGroupsInPlan = showGroupsInPlanType
notifyDataSetChanged()
}
}
@ -185,6 +186,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
}
override fun onDestroyView() {
timetableAdapter.resetTimers()
presenter.onDetachView()
super.onDestroyView()
}

View File

@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import java.lang.NullPointerException
import java.time.LocalDate
import java.time.LocalDate.now
import java.time.LocalDate.of
@ -35,7 +34,7 @@ class TimetablePresenter @Inject constructor(
private val timetableRepository: TimetableRepository,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<TimetableView>(errorHandler, studentRepository) {
private var baseDate: LocalDate = now().nextOrSameSchoolDay
@ -143,6 +142,7 @@ class TimetablePresenter @Inject constructor(
view?.apply {
updateData(
showWholeClassPlanType = prefRepository.showWholeClassPlan,
showGroupsInPlanType = prefRepository.showGroupsInPlan,
showTimetableTimers = prefRepository.showTimetableTimers,
data = it.data!!
.filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true }

View File

@ -12,7 +12,7 @@ interface TimetableView : BaseView {
fun initView()
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean)
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean)
fun updateNavigationDay(date: String)

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRe
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
@ -30,7 +30,7 @@ class CompletedLessonsPresenter @Inject constructor(
private val completedLessonsErrorHandler: CompletedLessonsErrorHandler,
private val semesterRepository: SemesterRepository,
private val completedLessonsRepository: CompletedLessonsRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: AnalyticsHelper
) : BasePresenter<CompletedLessonsView>(completedLessonsErrorHandler, studentRepository) {
private var baseDate: LocalDate = now().nextOrSameSchoolDay

View File

@ -24,7 +24,7 @@ import io.github.wulkanowy.services.HiltBroadcastReceiver
import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
@ -49,7 +49,7 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
lateinit var sharedPref: SharedPrefProvider
@Inject
lateinit var analytics: FirebaseAnalyticsHelper
lateinit var analytics: AnalyticsHelper
companion object {

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.utils
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory
/**
* [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji]
@ -23,4 +26,15 @@ private fun calculatePercentage(presence: Double, absence: Double): Double {
return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100
}
inline val Attendance.description
get() = when (AttendanceCategory.getCategoryByName(name)) {
AttendanceCategory.PRESENCE -> R.string.attendance_present
AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused
AttendanceCategory.ABSENCE_EXCUSED -> R.string.attendance_absence_excused
AttendanceCategory.UNEXCUSED_LATENESS -> R.string.attendance_unexcused_lateness
AttendanceCategory.EXCUSED_LATENESS -> R.string.attendance_excused_lateness
AttendanceCategory.ABSENCE_FOR_SCHOOL_REASONS -> R.string.attendance_absence_school
AttendanceCategory.EXEMPTION -> R.string.attendance_exemption
AttendanceCategory.DELETED -> R.string.attendance_deleted
else -> R.string.attendance_unknown
}

View File

@ -80,17 +80,13 @@ fun <T> flowWithResource(block: suspend () -> T) = flow {
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.loading())
try {
block()
.catch { emit(Resource.error(it)) }
.collect {
if (it.status != Status.LOADING) { // LOADING is already emitted
emit(it)
}
block()
.catch { emit(Resource.error(it)) }
.collect {
if (it.status != Status.LOADING) { // LOADING is already emitted
emit(it)
}
} catch (e: Throwable) {
emit(Resource.error(e))
}
}
}
fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {

View File

@ -22,32 +22,32 @@ private fun Bundle?.checkSavedState() = if (this == null) "(STATE IS NULL)" else
class ActivityLifecycleLogger : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity?) {
activity?.let { Timber.d("${it::class.java.simpleName} PAUSED") }
override fun onActivityPaused(activity: Activity) {
Timber.d("${activity::class.java.simpleName} PAUSED")
}
override fun onActivityResumed(activity: Activity?) {
activity?.let { Timber.d("${it::class.java.simpleName} RESUMED") }
override fun onActivityResumed(activity: Activity) {
Timber.d("${activity::class.java.simpleName} RESUMED")
}
override fun onActivityStarted(activity: Activity?) {
activity?.let { Timber.d("${it::class.java.simpleName} STARTED") }
override fun onActivityStarted(activity: Activity) {
Timber.d("${activity::class.java.simpleName} STARTED")
}
override fun onActivityDestroyed(activity: Activity?) {
activity?.let { Timber.d("${it::class.java.simpleName} DESTROYED") }
override fun onActivityDestroyed(activity: Activity) {
Timber.d("${activity::class.java.simpleName} DESTROYED")
}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
activity?.let { Timber.d("${it::class.java.simpleName} SAVED INSTANCE STATE") }
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
Timber.d("${activity::class.java.simpleName} SAVED INSTANCE STATE")
}
override fun onActivityStopped(activity: Activity?) {
activity?.let { Timber.d("${it::class.java.simpleName} STOPPED") }
override fun onActivityStopped(activity: Activity) {
Timber.d("${activity::class.java.simpleName} STOPPED")
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
activity?.let { Timber.d("${it::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}") }
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
Timber.d("${activity::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}")
}
}

View File

@ -4,20 +4,26 @@ import android.content.res.Resources
import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import okhttp3.internal.http2.StreamResetException
import java.io.InterruptedIOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
fun Resources.getString(error: Throwable) = when (error) {
is UnknownHostException -> getString(R.string.error_no_internet)
is SocketTimeoutException, is InterruptedIOException -> getString(R.string.error_timeout)
is SocketTimeoutException, is InterruptedIOException, is ConnectException, is StreamResetException -> getString(R.string.error_timeout)
is NotLoggedInException -> getString(R.string.error_login_failed)
is PasswordChangeRequiredException -> getString(R.string.error_password_change_required)
is ServiceUnavailableException -> getString(R.string.error_service_unavailable)
is FeatureDisabledException -> getString(R.string.error_feature_disabled)
is FeatureNotAvailableException -> getString(R.string.error_feature_not_available)
is VulcanException -> getString(R.string.error_unknown_uonet)
is ScrapperException -> getString(R.string.error_unknown_app)
else -> getString(R.string.error_unknown)
}

View File

@ -13,8 +13,7 @@ import java.time.Month
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern
import java.time.format.TextStyle.FULL_STANDALONE
import java.time.format.TextStyle.*
import java.time.format.TextStyle.FULL
import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous
@ -78,6 +77,12 @@ inline val LocalDate.nextOrSameSchoolDay: LocalDate
}
}
inline val LocalDate.startExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday
inline val LocalDate.endExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday.plusWeeks(4).minusDays(1)
inline val LocalDate.previousOrSameSchoolDay: LocalDate
get() {
return when (dayOfWeek) {

View File

@ -1,7 +1,6 @@
Wersja 0.20.1
- naprawiliśmy powiadomienie o szczęśliwym numerku
- naprawiliśmy przełączanie na nowy rok szkolny
- naprawiliśmy funkcję resetowania hasła
- dodaliśmy dziennik e-Skarżysko
Wersja 0.22.2
- naprawiliśmy problem z wyświetlaniem pozycji na liście ocen
- zmieniliśmy komunikaty o błędach, które powinny być teraz czytelniejsze dla większej liczby użytkowników
- zwiększyliśmy maksymalny czas, przez który aplikacja będzie próbowała łączyć się z dziennikiem do 1 minuty
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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